commit f0fee5ab585b53c8cc45ddfd12fd78d35275700b Author: 545522390@qq.com Date: Thu Jan 17 11:05:47 2019 +0800 2.0.0版本发布 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..b748c52 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +*.js linguist-language=php +*.css linguist-language=php +*.html linguist-language=php diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..03000dc --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +!.gitignore +!.gitattributes +*.DS_Store +*.idea +*.svn +*.git +/runtime +/log +/vendor +/static/upload +!composer.json +/composer.lock diff --git a/.htaccess b/.htaccess new file mode 100644 index 0000000..d9ee23c --- /dev/null +++ b/.htaccess @@ -0,0 +1,8 @@ + + Options +FollowSymlinks -Multiviews + RewriteEngine On + + RewriteCond %{REQUEST_FILENAME} !-d + RewriteCond %{REQUEST_FILENAME} !-f + RewriteRule ^(.*)$ index.php?/$1 [QSA,PT,L] + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..18dbe27 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2017 Anyon + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..4c00aca --- /dev/null +++ b/README.md @@ -0,0 +1,74 @@ +# pearProjectApi + +基于Vue.js实现的项目管理系统 + +需要配合[前端项目](https://github.com/a54552239/pearProject)使用,链接:https://github.com/a54552239/pearProject + +有不明白的地方的可以加群:275264059,或者联系我,QQ:545522390 +### 演示地址 +> [https://beta.vilson.xyz](https://beta.vilson.xyz) + +### 登陆 ### +账号:123456 密码:123456 +### 界面截图 +![1](https://static.vilson.xyz/overview/1.png) + +![1](https://static.vilson.xyz/overview/2.png) + +![1](https://static.vilson.xyz/overview/3.png) + +![1](https://static.vilson.xyz/overview/4.png) + +![1](https://static.vilson.xyz/overview/5.png) + +![1](https://static.vilson.xyz/overview/6.png) + +![1](https://static.vilson.xyz/overview/7.png) + +![1](https://static.vilson.xyz/overview/8.png) + +![1](https://static.vilson.xyz/overview/9.png) + +![1](https://static.vilson.xyz/overview/10.png) + +![1](https://static.vilson.xyz/overview/11.png) + +![1](https://static.vilson.xyz/overview/12.png) + +![1](https://static.vilson.xyz/overview/13.png) +### 安装步骤 ### +``` +PHP >= 7.0.0 (推荐PHP7.2版本) +Mysql >= 5.5.0 (需支持innodb引擎) +PDO PHP Extension +Node.js +Composer +``` +- 可以直接下载[phpstudy](http://phpstudy.php.cn/phpstudy/PhpStudy20180211.zip)部署环境 +1. 下载后端接口文件,解压到站点根目录(或使用Git: git clone https://github.com/a54552239/pearProjectApi) +2. 安装后端依赖 + 1. 进入接口文件目录 + 2. 方式一:Composer + 3. 方式二:下载[vendor.zip](https://static.vilson.xyz/help/pearproject/vendor.zip),直接解压到项目根目录,覆盖原有的vender文件夹 +3. 下载前端项目 +4. 安装node.js + 1. 地址:http://nodejs.cn/download/ 根据情况选择版本 + 2. 安装npm淘宝镜像 + 1. 运行cmd + 2. 输入:npm install -g cnpm --registry=https://registry.npm.taobao.org +5. 安装前端依赖 + 1. 进入前端项目目录,运行cmd命令行 + 2. 安装依赖:cnpm install + 1.如果接口端口不是默认端口,需修改./vue.config.js,将DEV_URL的值改为接口的访问地址 + 3. 启动项目:npm run serve + 4. 根据提示填写数据库信息进行安装 + ![1](https://static.vilson.xyz/help/pearproject/3.png) +6. 打包项目(有必要的话) + 1. 修改./src/config/config.js,修改PRO_URL地址 + 2. 修改./vue.config.js,将publicPath 值改为‘/’。如果有CDN的话改为CDN地址 + 3. 运行cmd,输入 npm run build + 4. 运行dist目录下的index.html,或者将dist目录下的文件部署到服务器上 +### 鼓励一下 ### +Sample + +Sample diff --git a/application/.htaccess b/application/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/application/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/application/common.php b/application/common.php new file mode 100644 index 0000000..e37f4df --- /dev/null +++ b/application/common.php @@ -0,0 +1,271 @@ +where([$fieldName => $code])->field($fieldName)->find(); + if ($has) { + return createUniqueCode($tableName, $fieldName, $len); + } + return $code; +} + +/** + * 设备或配置系统参数 + * @param string $name 参数名称 + * @param bool $value 默认是null为获取值,否则为更新 + * @return string|bool + * @throws \think\Exception + * @throws \think\exception\PDOException + */ +function sysconf($name, $value = null) +{ + static $config = []; + if ($value !== null) { + list($config, $data) = [[], ['name' => $name, 'value' => $value]]; + return DataService::save('SystemConfig', $data, 'name'); + } + if (empty($config)) { + $config = Db::name('SystemConfig')->column('name,value'); + } + return isset($config[$name]) ? $config[$name] : ''; +} + +/** + * 日期格式标准输出 + * @param string $datetime 输入日期 + * @param string $format 输出格式 + * @return false|string + */ +function format_datetime($datetime, $format = 'Y年m月d日 H:i:s') +{ + return date($format, strtotime($datetime)); +} + +function nowTime() +{ + return date('Y-m-d H:i:s', time()); + +} + +// 判断文件或目录是否有写的权限 +function is_really_writable($file) +{ + if (DIRECTORY_SEPARATOR == '/' AND @ ini_get("safe_mode") == FALSE) { + return is_writable($file); + } + if (!is_file($file) OR ($fp = @fopen($file, "r+")) === FALSE) { + return FALSE; + } + + fclose($fp); + return TRUE; +} + +/** + * UTF8字符串加密 + * @param string $string + * @return string + */ +function encode($string) +{ + list($chars, $length) = ['', strlen($string = iconv('utf-8', 'gbk', $string))]; + for ($i = 0; $i < $length; $i++) { + $chars .= str_pad(base_convert(ord($string[$i]), 10, 36), 2, 0, 0); + } + return $chars; +} + +/** + * UTF8字符串解密 + * @param string $string + * @return string + */ +function decode($string) +{ + $chars = ''; + foreach (str_split($string, 2) as $char) { + $chars .= chr(intval(base_convert($char, 36, 10))); + } + return @iconv('gbk', 'utf-8', $chars); +} + +/** + * 获取锁 + * @param String $key 锁标识 + * @param Int $expire 锁过期时间 + * @return Boolean + */ +function lock($key = '', $expire = 5) +{ + $is_lock = Cache::store('redis')->get($key); + //不能获取锁 + if (!$is_lock) { + Cache::store('redis')->set($key, time() + $expire); + } + + return $is_lock ? true : false; +} + +/** + * 释放锁 + * @param String $key 锁标识 + * @return Boolean + */ +function unlock($key = '') +{ + return Cache::store('redis')->rm($key); +} + +/** + * 下载远程文件到本地 + * @param string $url 远程图片地址 + * @return string + */ +function local_image($url) +{ + return \service\FileService::download($url)['url']; +} + +/** + * 提取base64 + * @param $base64_url + * @return array + */ +function decodeFile($base64_url) +{ + preg_match('/^data:image\/(\w+);base64/', $base64_url, $out); + + $type = $out[1]; + $type_param = 'data:image/' . $type . ';base64,'; + $fileStream = str_replace($type_param, '', $base64_url); + $fileStream = base64_decode($fileStream); + + return array( + 'type' => $type, + 'fileStream' => $fileStream + ); + +} + +//不同环境下获取真实的IP +function get_ip() +{ + //判断服务器是否允许$_SERVER + if (isset($_SERVER)) { + if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) { + $realip = $_SERVER['HTTP_X_FORWARDED_FOR']; + } elseif (isset($_SERVER['HTTP_CLIENT_IP'])) { + $realip = $_SERVER['HTTP_CLIENT_IP']; + } else { + $realip = $_SERVER['REMOTE_ADDR']; + } + } else { + //不允许就使用getenv获取 + if (getenv("HTTP_X_FORWARDED_FOR")) { + $realip = getenv("HTTP_X_FORWARDED_FOR"); + } elseif (getenv("HTTP_CLIENT_IP")) { + $realip = getenv("HTTP_CLIENT_IP"); + } else { + $realip = getenv("REMOTE_ADDR"); + } + } + + return $realip; +} + + +/** + * DES 加密 + * @param $dat 需要加密的字符串 + * @param $key 加密密钥 + * @return string + */ +function javaDesEncrypt($dat, $key) +{ + /*$block = mcrypt_get_block_size(MCRYPT_DES, MCRYPT_MODE_ECB); + $len = strlen($dat); + $padding = $block - ($len % $block); + $dat .= str_repeat(chr($padding),$padding); + return bin2hex(mcrypt_encrypt(MCRYPT_DES, $key, $dat, MCRYPT_MODE_ECB));*/ + + return bin2hex(openssl_encrypt($dat, 'des-ecb', $key, OPENSSL_RAW_DATA)); +} + +/** + * DES 解密 + * @param $dat 需要解密的字符串 + * @param $key 加密密钥 + * @return bool|string + */ +function javaDesDecrypt($dat, $key) +{ + /*$str = hex2bin($dat); + $str = mcrypt_decrypt(MCRYPT_DES, $key, $str, MCRYPT_MODE_ECB); + $pad = ord($str[($len = strlen($str)) - 1]); + return substr($str, 0, strlen($str) - $pad);*/ + + $str = hex2bin($dat); + return openssl_decrypt($str, 'des-ecb', $key, OPENSSL_RAW_DATA); +} + diff --git a/application/common/Model/Areas.php b/application/common/Model/Areas.php new file mode 100644 index 0000000..86eb079 --- /dev/null +++ b/application/common/Model/Areas.php @@ -0,0 +1,31 @@ +get('areadData'); + if (!$list) { + $list = self::where('id > 100000')->order('id asc')->select()->toArray(); + Cache::store('redis')->set('areadData', $list); + } + if ($list) { + $list = ToolsService::arr2tree($list, 'ID', 'ParentId'); + } + return $list; + } +} diff --git a/application/common/Model/Collection.php b/application/common/Model/Collection.php new file mode 100644 index 0000000..f7d665b --- /dev/null +++ b/application/common/Model/Collection.php @@ -0,0 +1,42 @@ + $code, 'type' => 'task', 'member_code' => $memberCode])->find(); + if ($star && !$stared) { + $data = [ + 'create_time' => nowTime(), + 'code' => createUniqueCode('collection'), + 'create_by' => $memberCode, + 'source_code' => $code, + 'type' => 'task', + 'member_code' => $memberCode, + ]; + return self::create($data); + } + if (!$star) { + return self::where(['source_code' => $code, 'type' => 'task', 'member_code' => $memberCode])->delete(); + } + } + +} diff --git a/application/common/Model/CommonModel.php b/application/common/Model/CommonModel.php new file mode 100644 index 0000000..cd3957a --- /dev/null +++ b/application/common/Model/CommonModel.php @@ -0,0 +1,151 @@ +where($where)->whereOr($whereOr)->order($order)->field($field)->paginate($rows, $simple, $config); + $list = $page->all(); + $result = ['total' => $simple ? count($list) : $page->total(), 'page' => $page->currentPage(), 'list' => $list]; + return $result; + } + + public function _listWithTrashed($where = null, $order = null, $field = null, $simple = false, $config = []) + { + $rows = intval(Request::param('rows', cookie('pageSize'))); + if (!$rows) { + $rows = 10; + } + cookie('pageSize', $rows); + $config['query'] = Request::param(); + $whereOr = []; + if (isset($where['or']) and $where['or']) { + //todo 怎么or连贯查询 + /* + * whereOr查询,形式如: + $where['or'][]= ['name','like',"xxx"]; + $where['or'][] = ['id','=',"xxx"]; + */ + $whereOr = $where['or']; + unset($where['or']); + } + $class = get_class($this); + $count = $class::withTrashed()->where($where)->whereOr($whereOr)->order($order)->field($field)->count(); + $page = $config['query']['page'] ? $config['query']['page'] : 1; + $offset = $rows * ($config['query']['page'] - 1); + $list = $class::withTrashed()->where($where)->whereOr($whereOr)->order($order)->field($field)->limit($offset, $rows)->select(); + $result = ['total' => $count, 'page' => $page, 'list' => $list]; + return $result; + } + + public function _edit($data, $where = []) + { + return $this->isUpdate(true)->save($data,$where); + } + + public function _add($data) + { + $obj = $this::create($data); + if ($obj->id) { + return $this::get($obj->id); + } + return false; + } + + /** + * @param File $file + * @param $path_name + * @return array|bool + * @throws \OSS\Core\OssException + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws \Exception + */ + public function _uploadImg(File $file, $path_name = '') + { + if (!$path_name) { + $path_name = config('upload.base_path') . config('default'); + } + if (!$file->checkExt(strtolower(sysconf('storage_local_exts')))) { + \exception('文件上传类型受限', 1); + } + $path = $path_name; + $info = $file->move($path); + if ($info) { + $filename = str_replace('\\', '/', $path . '/' . $info->getSaveName()); +// $image = \think\Image::open($info->getRealPath()); +// $image->thumb($image->width() / 2, $image->height() / 2)->save($filename);//压缩 + $site_url = FileService::getFileUrl($filename, 'local'); + $fileInfo = FileService::save($filename, file_get_contents($site_url)); + if ($fileInfo) { + return ['base_url' => $fileInfo['key'], 'url' => $fileInfo['url'], 'filename' => $file->getInfo('name')]; + } + } + return false; + } + + /* + * 获取当前organization id + * */ + public function gecurrentOrganizationCode(){ + $currentOrganizationCode = session('currentOrganizationCode'); + return $currentOrganizationCode; + } + + /* + * 获取当前member session + * */ + public function getMemberSession(){ + $member_session = session('member'); + return $member_session; + } + +} diff --git a/application/common/Model/Department.php b/application/common/Model/Department.php new file mode 100644 index 0000000..2d808ed --- /dev/null +++ b/application/common/Model/Department.php @@ -0,0 +1,63 @@ + $parentDepartmentCode])->field('code,path')->find(); + $parentDepartment['path'] && $parentDepartment['path'] = ",{$parentDepartment['path']}"; + $path = "{$parentDepartment['code']}{$parentDepartment['path']}"; + } + $data = [ + 'organization_code' => getCurrentOrganizationCode(), + 'code' => createUniqueCode('department'), + 'name' => $name, + 'pcode' => $parentDepartmentCode, + 'path' => $path, + 'create_time' => nowTime(), + ]; + return self::create($data); + } + + public function deleteDepartment($departmentCode) + { + $department = self::where(['code' => $departmentCode])->find(); + if (!$department) { + throw new \Exception('该部门不存在', 1); + } + $prefix = config('database.prefix'); + $sql = "select code from {$prefix}department where find_in_set('{$departmentCode}',path)"; + $departments = Db::name('department')->query($sql); + $codes = [$departmentCode]; + if ($departments) { + foreach ($departments as $department) { + $codes[] = $department['code']; + } + } + $result = self::whereIn('code', $codes)->delete(); + DepartmentMember::whereIn('department_code', $codes)->delete(); + return $result; + } +} diff --git a/application/common/Model/DepartmentMember.php b/application/common/Model/DepartmentMember.php new file mode 100644 index 0000000..3f80c11 --- /dev/null +++ b/application/common/Model/DepartmentMember.php @@ -0,0 +1,111 @@ + $departmentCode])->find(); + if (!$department) { + throw new \Exception('该部门不存在', 1); + } + $hasJoined = self::where(['account_code' => $accountCode, 'department_code' => $departmentCode])->find(); + if ($hasJoined) { + throw new \Exception('已加入该部门', 2); + } + $data = [ + 'code' => createUniqueCode('departmentMember'), + 'account_code' => $accountCode, + 'organization_code' => $orgCode, + 'department_code' => $departmentCode, + 'is_owner' => $isOwner, + 'is_principal' => $isPrincipal, + 'join_time' => nowTime() + ]; + $result = self::create($data); + $department_codes = self::where(['account_code' => $accountCode, 'organization_code' => $orgCode])->column('department_code'); + if ($department_codes) { + $department_codes = implode(',', $department_codes); + MemberAccount::update(['department_code' => $department_codes], ['code' => $accountCode]); + } + return $result; + } else { + $hasJoined = MemberAccount::where(['member_code' => $accountCode, 'organization_code' => $orgCode])->find(); + if ($hasJoined) { + throw new \Exception('已加入该组织', 3); + } + $memberDate = Member::where(['code' => $accountCode])->find(); + if (!$memberDate) { + throw new \Exception('该用户不存在', 4); + } + $auth = ProjectAuth::where(['organization_code' => $orgCode, 'is_default' => 1])->field('id')->find(); + $authId = ''; + if ($auth) { + $authId = $auth['id'];//权限id + } + $data = [ + 'position' => '资深工程师', + 'department' => '某某公司-某某某事业群-某某平台部-某某技术部', + 'code' => createUniqueCode('memberAccount'), + 'member_code' => $accountCode, + 'organization_code' => $orgCode, + 'is_owner' => 0, + 'authorize' => $authId, + 'status' => 1, + 'create_time' => nowTime(), + 'name' => $memberDate['name'], + 'email' => $memberDate['email'], + ]; + return MemberAccount::create($data); + } + } + + /** + * @param $accountCode + * @param $departmentCode + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function removeMember($accountCode, $departmentCode) + { + $orgCode = getCurrentOrganizationCode(); + $department = Department::where(['code' => $departmentCode])->find(); + if (!$department) { + throw new \Exception('该部门不存在', 1); + } + $hasJoined = self::where(['account_code' => $accountCode, 'department_code' => $departmentCode])->find(); + if (!$hasJoined) { + throw new \Exception('尚未加入该部门', 2); + } + $result = $hasJoined->delete(); + $department_codes = self::where(['account_code' => $accountCode, 'organization_code' => $orgCode])->column('department_code'); + if ($department_codes) { + $department_codes = implode(',', $department_codes); + MemberAccount::update(['department_code' => $department_codes], ['code' => $accountCode]); + } + return $result; + } +} diff --git a/application/common/Model/File.php b/application/common/Model/File.php new file mode 100644 index 0000000..1091ba6 --- /dev/null +++ b/application/common/Model/File.php @@ -0,0 +1,119 @@ + $projectCode])->find(); + if (!$project) { + throw new \Exception('该项目已失效', 1); + } + $memberCode = getCurrentMember()['code']; + $orgCode = getCurrentOrganizationCode(); + $fileData = [ + 'code' => createUniqueCode('file'), + 'create_by' => $memberCode, + 'project_code' => $projectCode, + 'organization_code' => $orgCode, + 'path_name' => isset($data['path_name']) ? $data['path_name'] : '', + 'title' => isset($data['title']) ? $data['title'] : '', + 'extension' => isset($data['extension']) ? $data['extension'] : '', + 'size' => isset($data['size']) ? $data['size'] : '', + 'object_type' => isset($data['object_type']) ? $data['object_type'] : '', + 'extra' => isset($data['extra']) ? $data['extra'] : '', + 'file_url' => isset($data['file_url']) ? $data['file_url'] : '', + 'file_type' => isset($data['file_type']) ? $data['file_type'] : '', + 'create_time' => nowTime(), + ]; + $result = self::create($fileData); + return $result; + } + + /** + * 放入回收站 + * @param $code + * @return File + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recycle($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('文件不存在', 1); + } + if ($info['deleted']) { + throw new \Exception('文件已在回收站', 2); + } + $result = self::update(['deleted' => 1, 'deleted_time' => nowTime()], ['code' => $code]); + return $result; + } + + /** + * 恢复文件 + * @param $code + * @return File + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recovery($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('文件不存在', 1); + } + if (!$info['deleted']) { + throw new \Exception('文件已恢复', 2); + } + $result = self::update(['deleted' => 0], ['code' => $code]); + return $result; + } + + public function deleteFile($code) + { + //todo 权限判断 + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('文件不存在', 1); + } + Db::startTrans(); + try { + self::where(['code' => $code])->delete(); + //todo 删除物理文件 + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage()); + } + return true; + } + + public function getFullNameAttr($value, $data) + { + return "{$data['title']}.{$data['extension']}"; + } + +} diff --git a/application/common/Model/Member.php b/application/common/Model/Member.php new file mode 100644 index 0000000..c64a313 --- /dev/null +++ b/application/common/Model/Member.php @@ -0,0 +1,95 @@ +where($where)->find(); + } + + /** + * @param $memberData + * @return Member + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public static function createMember($memberData) + { + //需要创建的信息。1、用户 2、用户所属组织 3、组织权限 4、所属组织账号 + $memberData['create_time'] = nowTime(); + $result = self::create($memberData); + + + Organization::createOrganization($result); +// $organizationData = [ +// 'code' => createUniqueCode('organization'), +// 'name' => $memberData['name'] . '的个人项目', +// 'personal' => 1, +// 'create_time' => nowTime(), +// 'owner_code' => $memberData['code'], +// ]; +// Organization::create($organizationData); +// +// $defaultAdminAuth = ProjectAuth::get(1)->toArray(); +// $defaultMemberAuth = ProjectAuth::get(2)->toArray(); +// unset($defaultAdminAuth['id']); +// unset($defaultMemberAuth['id']); +// $defaultAdminAuth['organization_code'] = $defaultMemberAuth['organization_code'] = $organizationData['code']; +// $defaultAdminAuth = ProjectAuth::create($defaultAdminAuth); +// $defaultMemberAuth = ProjectAuth::create($defaultMemberAuth); +// $defaultAdminAuthNode = ProjectAuthNode::where(['auth' => 1])->select()->toArray(); +// $defaultMemberAuthNode = ProjectAuthNode::where(['auth' => 2])->select()->toArray(); +// foreach ($defaultAdminAuthNode as &$item) { +// unset($item['id']); +// $item['auth'] = $defaultAdminAuth['id']; +// ProjectAuthNode::create($item); +// } +// foreach ($defaultMemberAuthNode as &$item) { +// unset($item['id']); +// $item['auth'] = $defaultMemberAuth['id']; +// ProjectAuthNode::create($item); +// } +// +// $memberAccountData = [ +// 'position' => '资深工程师', +// 'department' => '某某公司-某某某事业群-某某平台部-某某技术部-BM', +// 'code' => createUniqueCode('organization'), +// 'member_code' => $memberData['code'], +// 'organization_code' => $organizationData['code'], +// 'is_owner' => 1, +// 'status' => 1, +// 'create_time' => nowTime(), +// 'avatar' => $memberData['avatar'], +// 'name' => $memberData['name'], +// 'email' => $memberData['email'], +// ]; +// MemberAccount::create($memberAccountData); + return $result; + } + + + /** + * @param File $file + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws \Exception + */ + public function uploadImg(File $file) + { + return $this->_uploadImg($file, config('upload.base_path') . config('upload.member_avatar')); + } +} diff --git a/application/common/Model/MemberAccount.php b/application/common/Model/MemberAccount.php new file mode 100644 index 0000000..8e041f6 --- /dev/null +++ b/application/common/Model/MemberAccount.php @@ -0,0 +1,85 @@ +listForUser(); + return $list; + } + + /** + * @param File $file + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws \Exception + */ + public function uploadImg(File $file) + { + return $this->_uploadImg($file, config('upload.base_path') . config('upload.member_avatar')); + } + + public function getAccountByOrganization($account, $organization_code) + { + return $this->where(['account' => $account, 'organization_code' => $organization_code])->find(); + } + + public function getAuthorizeArrAttr($value, $data) + { + //支持同时设置多个角色,默认关闭 + if ($data['authorize']) { + return explode(',', $data['authorize']); + } + return []; + } + + public function getStatusTextAttr($value, $data) + { + $status = [0 => '禁用', 1 => '使用中']; + return $status[$data['status']]; + } + + /** + * @param $accountCode + * @return bool + * @throws \Exception + */ + public function del($accountCode) + { + //todo 权限判断 + try { + Db::startTrans(); + $memberAccount = self::where(['code' => $accountCode])->find()->toArray(); + self::destroy(['code' => $accountCode]); + $projects = Project::where(['organization_code' => $memberAccount['organization_code']])->column('code'); + if ($projects) { + ProjectMember::whereIn('project_code', $projects)->where(['member_code' => $memberAccount['member_code']])->delete(); + $orgCode = getCurrentOrganizationCode(); + DepartmentMember::where(['account_code' => $accountCode, 'organization_code' => $orgCode])->delete(); + } + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage(), 201); + } + return true; + } +} diff --git a/application/common/Model/Notify.php b/application/common/Model/Notify.php new file mode 100644 index 0000000..d70e117 --- /dev/null +++ b/application/common/Model/Notify.php @@ -0,0 +1,90 @@ +where($where)->order('id desc')->select(); + $formatList = []; + $total = $this->where($where)->count('id'); + if ($list) { + foreach ($list as &$item) { + foreach ($types as $type) { + !isset($formatList[$type]) and $formatList[$type] = []; + !isset($totalSum[$type]) and $totalSum[$type] = 0; + $sum = $this->where($where)->where(['type'=>$type])->count('id'); + $totalSum[$type] = $sum; + if ($size and count($formatList[$type]) >= $size) { + continue; + } + if ($item['type'] == $type) { + $item['from'] = $this->getReceiverByTerminal($item['terminal'], $item['from']); + $item['to'] = $this->getReceiverByTerminal($item['terminal'], $item['to']); + $formatList[$type][] = $item; +// $total++; + } + } + } + } + return ['list' => $formatList, 'total' => $total, 'totalSum' => $totalSum]; + } + + public function getReceiverByTerminal($terminal, $to) + { + if (!$to) { + return false; + } + switch ($terminal) { + case 'system': + return []; + } + } + + public function getFromByType($fromType, $from) + { + if (!$from) { + return false; + } + switch ($fromType) { + case 'admin': + return SystemUser::find($from); + case 'project': //消息 + return MemberAccount::find($from); + } + } + + public function add($title, $content, $type, $from, $to, $action, $send_data, $terminal,$fromType = 'system') + { + $data = [ + 'title' => $title, + 'content' => $content, + 'type' => $type, + 'from' => $from, + 'to' => $to, + 'action' => $action, + 'send_data' => $send_data, + 'terminal' => $terminal, + 'from_type' => $fromType, + 'create_time' => nowTime(), + ]; + return self::create($data); + } +} diff --git a/application/common/Model/Organization.php b/application/common/Model/Organization.php new file mode 100644 index 0000000..1d3f1f0 --- /dev/null +++ b/application/common/Model/Organization.php @@ -0,0 +1,87 @@ +toArray(); + $defaultMemberAuth = ProjectAuth::get($defaultMemberAuthId)->toArray(); + unset($defaultAdminAuth['id']); + unset($defaultMemberAuth['id']); + $defaultAdminAuth['organization_code'] = $defaultMemberAuth['organization_code'] = $data['code']; + $defaultAdminAuth = ProjectAuth::create($defaultAdminAuth); + $defaultMemberAuth = ProjectAuth::create($defaultMemberAuth); + $defaultAdminAuthNode = ProjectAuthNode::where(['auth' => $defaultAdminAuthId])->select()->toArray(); + $defaultMemberAuthNode = ProjectAuthNode::where(['auth' => $defaultMemberAuthId])->select()->toArray(); + foreach ($defaultAdminAuthNode as &$item) { + unset($item['id']); + $item['auth'] = $defaultAdminAuth['id']; + ProjectAuthNode::create($item); + } + foreach ($defaultMemberAuthNode as &$item) { + unset($item['id']); + $item['auth'] = $defaultMemberAuth['id']; + ProjectAuthNode::create($item); + } + + $memberAccountData = [ + 'position' => '资深工程师', + 'department' => '某某公司-某某某事业群-某某平台部-某某技术部-BM', + 'code' => createUniqueCode('organization'), + 'member_code' => $memberData['code'], + 'organization_code' => $data['code'], + 'is_owner' => 1, + 'status' => 1, + 'create_time' => nowTime(), + 'avatar' => $memberData['avatar'], + 'name' => $memberData['name'], + 'email' => $memberData['email'], + ]; + MemberAccount::create($memberAccountData); + return $organization; + } + + + public function edit($code, $data) + { + if (!$code) { + throw new \Exception('请选择组织', 1); + } + $project = self::where(['code' => $code])->field('id', true)->find(); + if (!$project) { + throw new \Exception('该组织不存在', 1); + } + $result = self::update($data, ['code' => $code]); + return $result; + } +} diff --git a/application/common/Model/Project.php b/application/common/Model/Project.php new file mode 100644 index 0000000..369f84e --- /dev/null +++ b/application/common/Model/Project.php @@ -0,0 +1,259 @@ + '待处理'], ['name' => '进行中'], ['name' => '已完成']]; + + public static function getEffectInfo($id) + { + return self::where(['id' => $id, 'deleted' => 0, 'archive' => 0])->find(); + } + + public function getMemberProjects($memberCode = '', $deleted = 0, $page = 1, $pageSize = 10) + { + if (!$memberCode) { + $memberCode = getCurrentMember()['code']; + } + if ($page < 1) { + $page = 1; + } + $offset = ($page - 1) * $page; + $limit = $pageSize; + $prefix = config('database.prefix'); + $sql = "select *,p.id as id,p.name as name,p.code as code from {$prefix}project as p join {$prefix}project_member as pm on p.code = pm.project_code where pm.member_code = '{$memberCode}' and p.deleted = {$deleted} order by p.id desc"; + $total = Db::query($sql); + $total = count($total); + $sql .= " limit {$offset},{$limit}"; + $list = Db::query($sql); + return ['list' => $list, 'total' => $total]; + } + + /** + * 创建项目 + * @param $memberCode + * @param $orgCode + * @param $name + * @param string $description + * @param string $templateCode + * @return Project + * @throws \Exception + */ + public function createProject($memberCode, $orgCode, $name, $description = '', $templateCode = '') + { + //d85f1bvwpml2nhxe94zu7tyi + Db::startTrans(); + try { + $project = [ + 'create_time' => nowTime(), + 'code' => createUniqueCode('project'), + 'name' => $name, + 'description' => $description, + 'organization_code' => $orgCode, + 'cover' => FileService::getFilePrefix() . 'static/image/default/project-cover.png' + ]; + $result = self::create($project); + $projectMemberModel = new ProjectMember(); + $projectMemberModel->inviteMember($memberCode, $project['code'], 1); + if ($templateCode) { + $stages = TaskStagesTemplate::where(['project_template_code' => $templateCode])->order('sort desc,id asc')->select(); + } else { + $stages = $this->defaultStages; + } + if ($stages) { + foreach ($stages as $key => $stage) { + $taskStage = [ + 'project_code' => $project['code'], + 'name' => $stage['name'], + 'sort' => $key, + 'code' => createUniqueCode('taskStages'), + 'create_time' => nowTime(), + ]; + $stagesResult = TaskStages::create($taskStage); + $taskStage['id'] = $stagesResult['id']; + } + } + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage(), 1); + } + self::projectHook(getCurrentMember()['code'], $project['code'], 'create'); + return $result; + } + + public function edit($code, $data) + { + if (!$code) { + throw new \Exception('请选择项目', 1); + } + $project = self::where(['code' => $code, 'deleted' => 0])->field('id', true)->find(); + if (!$project) { + throw new \Exception('该项目在回收站中无法编辑', 1); + } + $result = self::update($data, ['code' => $code]); + //TODO 项目动态 + self::projectHook(getCurrentMember()['code'], $code, 'edit'); + return $result; + } + + + /** + * @param File $file + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws \Exception + */ + public function uploadCover(File $file) + { + return $this->_uploadImg($file); + } + + /** + * 放入回收站 + * @param $code + * @return Project + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recycle($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('项目不存在', 1); + } + if ($info['deleted']) { + throw new \Exception('项目已在回收站', 2); + } + $result = self::update(['deleted' => 1, 'deleted_time' => nowTime()], ['code' => $code]); + self::projectHook(getCurrentMember()['code'], $code, 'recycle'); + return $result; + } + + /** + * 恢复项目 + * @param $code + * @return Project + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recovery($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('项目不存在', 1); + } + if (!$info['deleted']) { + throw new \Exception('项目已恢复', 2); + } + $result = self::update(['deleted' => 0], ['code' => $code]); + self::projectHook(getCurrentMember()['code'], $code, 'recovery'); + return $result; + } + + /** + * 项目归档 + * @param $code + * @return Project + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function archive($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('项目不存在', 1); + } + if ($info['archive']) { + throw new \Exception('项目已归档', 2); + } + $result = self::update(['archive' => 1, 'archive_time' => nowTime()], ['code' => $code]); + self::projectHook(getCurrentMember()['code'], $code, 'archive'); + return $result; + } + + /** + * 恢复项目 + * @param $code + * @return Project + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recoveryArchive($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('项目不存在', 1); + } + if (!$info['archive']) { + throw new \Exception('项目已恢复', 2); + } + $result = self::update(['archive' => 0], ['code' => $code]); + self::projectHook(getCurrentMember()['code'], $code, 'recoveryArchive'); + return $result; + } + + /** + * 退出项目 + * @param $code + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \Exception + */ + public function quit($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('项目不存在', 1); + } + $where = ['project_code' => $code, 'member_code' => getCurrentMember()['code']]; + $projectMember = ProjectMember::where($where)->find(); + if (!$projectMember) { + throw new \Exception('你不是该项目成员', 2); + } + if ($projectMember['is_owner']) { + throw new \Exception('创建者不能退出项目', 3); + } + $result = ProjectMember::where($where)->delete(); + return $result; + } + + /** 项目变动钩子 + * @param $memberCode + * @param $sourceCode + * @param string $type + * @param string $toMemberCode + * @param int $isComment + * @param string $remark + * @param string $content + * @param string $fileCode + * @param array $data + * @param string $tag + */ + public static function projectHook($memberCode, $sourceCode, $type = 'create', $toMemberCode = '', $isComment = 0, $remark = '', $content = '', $fileCode = '', $data = [], $tag = 'project') + { + $data = ['memberCode' => $memberCode, 'sourceCode' => $sourceCode, 'remark' => $remark, 'type' => $type, 'content' => $content, 'isComment' => $isComment, 'toMemberCode' => $toMemberCode, 'fileCode' => $fileCode, 'data' => $data, 'tag' => $tag]; + Hook::listen($tag, $data); + + } +} diff --git a/application/common/Model/ProjectAuth.php b/application/common/Model/ProjectAuth.php new file mode 100644 index 0000000..561142f --- /dev/null +++ b/application/common/Model/ProjectAuth.php @@ -0,0 +1,46 @@ + $id]; + $result = ProjectAuthNode::where($where)->delete(); + if ($result !== false) { + return true; + } + } + return false; + } + + public function getIdAttr($value) + { + return strval($value); + } + + public function getStatusTextAttr($value, $data) + { + $status = [0 => '禁用', 1 => '使用中']; + return $status[$data['status']]; + } + + public function getCanDeleteAttr($value, $data) + { + if ($data['type'] == 'admin' || $data['type'] == 'member') { + return 0; + } + return 1; + } +} diff --git a/application/common/Model/ProjectAuthNode.php b/application/common/Model/ProjectAuthNode.php new file mode 100644 index 0000000..ab69971 --- /dev/null +++ b/application/common/Model/ProjectAuthNode.php @@ -0,0 +1,13 @@ + $projectCode, 'deleted' => 0])->find(); + if (!$project) { + throw new \Exception('该项目已失效', 1); + } + $hasCollected = self::where(['member_code' => $memberCode, 'project_code' => $projectCode])->find(); + if ($type == 'collect') { + if ($hasCollected) { + throw new \Exception('该项目已收藏', 1); + } + $data = [ + 'member_code' => $memberCode, + 'project_code' => $projectCode, + 'create_time' => nowTime() + ]; + return self::create($data); + } else { + if (!$hasCollected) { + throw new \Exception('尚未收藏该项目', 1); + } + return self::where(['member_code' => $memberCode, 'project_code' => $projectCode])->delete(); + } + } +} diff --git a/application/common/Model/ProjectLog.php b/application/common/Model/ProjectLog.php new file mode 100644 index 0000000..23d1f18 --- /dev/null +++ b/application/common/Model/ProjectLog.php @@ -0,0 +1,8 @@ + $projectCode, 'deleted' => 0])->find(); + if (!$project) { + throw new \Exception('该项目已失效', 1); + } + $hasJoined = self::where(['member_code' => $memberCode, 'project_code' => $projectCode])->find(); + if ($hasJoined) { +// throw new \Exception('该成员已加入项目', 1); + return true; + } + $data = [ + 'member_code' => $memberCode, + 'project_code' => $projectCode, + 'is_owner' => $isOwner, + 'join_time' => nowTime() + ]; + $result = self::create($data); + Project::projectHook(getCurrentMember()['code'], $projectCode, 'inviteMember', $memberCode); + return $result; + } + + public function removeMember($memberCode, $projectCode) + { + $project = Project::where(['code' => $projectCode, 'deleted' => 0])->find(); + if (!$project) { + throw new \Exception('该项目已失效', 1); + } + $hasJoined = self::where(['member_code' => $memberCode, 'project_code' => $projectCode])->find(); + if (!$hasJoined) { +// throw new \Exception('该成员尚未加入项目', 1); + return true; + } + $result = $hasJoined->delete(); + Project::projectHook(getCurrentMember()['code'], $projectCode, 'removeMember', $memberCode); + return $result; + } +} diff --git a/application/common/Model/ProjectMenu.php b/application/common/Model/ProjectMenu.php new file mode 100644 index 0000000..e287d12 --- /dev/null +++ b/application/common/Model/ProjectMenu.php @@ -0,0 +1,189 @@ + '禁用', 1 => '使用中']; + return $status[$data['status']]; + } + + public function getInnerTextAttr($value, $data) + { + $status = [0 => '导航', 1 => '内页']; + return $status[$data['is_inner']]; + } + + public function getFullUrlAttr($value, $data) + { + + if (($data['params'] and $data['values'] != null) or $data['values'] != '') { + $fullUrl = $data['url'] . '/' . $data['values']; + return $fullUrl; + } + return $data['url']; + } + + public function childrenMenu() + { + return $this->hasMany('menu', 'pid')->selfRelation(); + } + + /** + * 获取所有菜单列表 + * @return array|string|\think\Collection + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function treeList() + { + $list = $this->order('sort asc,id asc')->select(); + $list = $list->toArray(); + if ($list) { + foreach ($list as &$item) { + $item['is_inner'] = !!$item['is_inner']; + $item['show_slider'] = !!$item['show_slider']; + unset($item); + } + } + $list = ToolsService::arr2tree($list); + return $list; + } + + /** + * 获取用户对应的菜单列表 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function listForUser() + { + NodeService::applyProjectAuthNode(); + $list = $this->where(['status' => '1'])->order('sort asc,id asc')->select(); + $list = $list->toArray(); + if ($list) { + foreach ($list as &$item) { + $item['is_inner'] = !!$item['is_inner']; + unset($item); + } + } + //主账号不做过滤 + $menus = session('member.is_owner') ? $list : $this->filterMenu($list, session('member.nodes')); + $new = []; + $this->buildFilterMenuData(ToolsService::arr2tree($menus), $new); + $menus = ToolsService::arr2tree($new); + return $menus; + } + + /** + * 过滤没有节点权限的菜单 + * @param $menus array 待过滤菜单 + * @param $nodes array 拥有的权限节点 + * @return array + */ + private function filterMenu($menus, $nodes) + { + $newMenus = []; + foreach ($menus as $key => $menu) { + if ($menu['node'] == '#') { + $newMenus[] = $menu; + } elseif (preg_match('/^https?\:/i', $menu['url'])) { + $newMenus[] = $menu; + continue; + } elseif ($menu['node'] != '#') { + $node = join('/', array_slice(explode('/', preg_replace('/[\W]/', '/', $menu['node'])), 0, 3)); + if ($nodes && in_array($node, $nodes)) { + $newMenus[] = $menu; + } + } + } + return $newMenus; + } + + /** + * 后台主菜单权限过滤(过滤没有子节点的菜单) + * @param array $menus 当前树形结构的菜单列表 + * @param $new array 过滤后的菜单 + * @return void + */ + private function buildFilterMenuData($menus, &$new) + { + + foreach ($menus as $key => $menu) { + if (($menu['node'] == '#' && isset($menu['children']) && $menu['children']) || ($menu['node'] != '#' && !isset($menu['children'])) || $menu['url'] == 'home') { + $temp = $menu; + unset($temp['children']); + $new[] = $temp; + } + if (isset($menu['children']) && $menu['children']) { + $this->buildFilterMenuData($menu['children'], $new); + } + } + } + + /** + * 后台主菜单权限过滤 + * @param array $menus 当前菜单列表 + * @param array $nodes 系统权限节点数据 + * @param bool $isLogin 是否已经登录 + * @return array + */ + private function buildMenuData($menus, $nodes, $isLogin) + { + + foreach ($menus as $key => &$menu) { + !empty($menu['children']) && $menu['children'] = $this->buildMenuData($menu['children'], $nodes, $isLogin); + if (!empty($menu['children'])) { + $menu['url'] = '#'; + } elseif (preg_match('/^https?\:/i', $menu['url'])) { + continue; + } elseif ($menu['node'] != '#') { + $node = join('/', array_slice(explode('/', preg_replace('/[\W]/', '/', $menu['node'])), 0, 3)); + if (!in_array($node, $nodes)) { + array_splice($menus, $key, 1); + continue; + } + if (in_array($node, $nodes) && $nodes[$node]['is_login'] && empty($isLogin)) { + array_splice($menus, $key, 1); + } elseif (in_array($node, $nodes) && $nodes[$node]['is_auth'] && $isLogin && !auth($node)) { + array_splice($menus, $key, 1); + } + } else { + array_splice($menus, $key, 1); + } + } + return $menus; + } + + public function del($id) + { + $delArr = [$id]; + $list = $this::where(['pid' => $id])->select()->toArray(); + if ($list) { + foreach ($list as $item) { + $delArr[] = $item['id']; + $list2 = $this::where(['pid' => $item['id']])->select()->toArray(); + if ($list2) { + foreach ($list2 as $item2) { + $delArr[] = $item2['id']; + } + } + } + } + return $this::destroy($delArr); + } +} diff --git a/application/common/Model/ProjectNode.php b/application/common/Model/ProjectNode.php new file mode 100644 index 0000000..eaf0547 --- /dev/null +++ b/application/common/Model/ProjectNode.php @@ -0,0 +1,7 @@ + nowTime(), + 'code' => createUniqueCode('projectTemplate'), + 'member_code' => $memberCode, + 'name' => $name, + 'description' => $description, + 'organization_code' => $orgCode, + 'cover' => $cover ?? FileService::getFilePrefix() . 'static/image/default/cover.png' + ]; + $result = self::create($data); + if ($result) { + $taskStagesList = TaskStagesTemplate::$defaultTaskStagesNameList; + if ($taskStagesList) { + foreach ($taskStagesList as $name) { + TaskStagesTemplate::createTaskStagesTemplate($data['code'], $name); + } + } + } + return $result; + } + + /** + * 删除模板 + * @param $code + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function deleteTemplate($code) + { + $template = self::where(['code' => $code])->field('id')->find(); + if (!$template) { + throw new \Exception('该模板不存在', 1); + } + $result = self::destroy(['code' => $code]); + if (!$result) { + throw new \Exception('删除失败', 2); + } + return TaskStagesTemplate::destroy(['project_template_code' => $code]); + } + + /** + * @param File $file + * @return array|bool + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws \Exception + */ + public function uploadCover(File $file) + { + return $this->_uploadImg($file); + } +} diff --git a/application/common/Model/SourceLink.php b/application/common/Model/SourceLink.php new file mode 100644 index 0000000..3b62e2d --- /dev/null +++ b/application/common/Model/SourceLink.php @@ -0,0 +1,95 @@ + $sourceCode])->find(); + } + if (!$source) { + throw new \Exception('该资源不存在', 1); + } + switch ($linkType) { + case 'task': + $link = Task::where(['code' => $linkCode])->find(); + } + if (!$link) { + throw new \Exception('关联主体不存在', 2); + } + $memberCode = getCurrentMember()['code']; + $orgCode = getCurrentOrganizationCode(); + $fileData = [ + 'code' => createUniqueCode('sourceLink'), + 'create_by' => $memberCode, + 'organization_code' => $orgCode, + 'source_type' => $sourceType, + 'source_code' => $sourceCode, + 'link_type' => $linkType, + 'link_code' => $linkCode, + 'sort' => $sort, + 'create_time' => nowTime(), + ]; + $result = self::create($fileData); + if ($linkType == 'task') { + Task::taskHook(getCurrentMember()['code'], $linkCode, 'linkFile', '', 0, '', '', '', ['title' => $source['fullName'], 'url' => $source['file_url']]); + } + return $result; + } + + public static function getSourceDetail($sourceCode) + { + $source = self::where(['code' => $sourceCode])->find(); + $sourceDetail = null; + switch ($source['source_type']) { + case 'file': + $source['title'] = ''; + $sourceDetail = File::where(['code' => $source['source_code']])->field('id', true)->find(); + if ($sourceDetail) { + $source['title'] = $sourceDetail['title']; + $project = Project::where(['code' => $sourceDetail['project_code']])->field('name')->find(); + $sourceDetail['projectName'] = $project['name']; + } + } + $source['sourceDetail'] = $sourceDetail; + return $source; + } + + public function deleteSource($code) + { + $source = self::where(['code' => $code])->find(); + if (!$source) { + throw new \Exception('该资源不存在', 1); + } + $source = self::getSourceDetail($code); + $result = self::where(['code' => $code])->delete(); + if ($source['link_type'] == 'task') { + Task::taskHook(getCurrentMember()['code'], $source['link_code'], 'unlinkFile', '', 0, '', '', '', ['title' => $source['title'], 'url' => $source['sourceDetail']['file_url']]); + } + return $result; + } + +} diff --git a/application/common/Model/SystemConfig.php b/application/common/Model/SystemConfig.php new file mode 100644 index 0000000..1ed3848 --- /dev/null +++ b/application/common/Model/SystemConfig.php @@ -0,0 +1,18 @@ +select(); + $data = []; + foreach ($config as $item) { + $data[$item['name']] = $item['value']; + } + return $data; + } +} diff --git a/application/common/Model/SystemLog.php b/application/common/Model/SystemLog.php new file mode 100644 index 0000000..5030952 --- /dev/null +++ b/application/common/Model/SystemLog.php @@ -0,0 +1,13 @@ +group('action')->column('action'); + } +} diff --git a/application/common/Model/Task.php b/application/common/Model/Task.php new file mode 100644 index 0000000..1a2d820 --- /dev/null +++ b/application/common/Model/Task.php @@ -0,0 +1,584 @@ + $code])->field('id', true)->find(); + if (!$task) { + throw new \Exception('该任务已失效', 404); + } + $project = Project::where(['code' => $task['project_code']])->field('name')->find(); + $stage = TaskStages::where(['code' => $task['stage_code']])->field('name')->find(); + $task['executor'] = null; + if ($task['assign_to']) { + $task['executor'] = Member::where(['code' => $task['assign_to']])->field('name,code,avatar')->find(); + } + if ($task['pcode']) { + $task['parentTask'] = self::where(['code' => $task['pcode']])->field('id', true)->find(); + } + $task['projectName'] = $project['name']; + $task['stageName'] = $stage['name']; + //TODO 查看权限 + return $task; + } + + /** + * @param $projectCode + * @param $deleted + * @throws \think\exception\DbException + */ + public function listForProject($projectCode, $deleted) + { + $this->_list($where); + } + + public function dateTotalForProject($projectCode, $beginTime = '', $endTime = '') + { + !$beginTime && $beginTime = date("Y-m-d", strtotime("-20 day")); + !$endTime && $endTime = nowTime(); + $dateList = DateService::getDateFromRange($beginTime, $endTime); + $list = []; + if ($dateList) { + foreach ($dateList as $date) { + $currentDate = "{$date} 00:00:00"; + $currentDateEnd = "{$date} 23:59:59"; + $total = Task::where("project_code = '{$projectCode}' and (create_time between '{$currentDate}' and '{$currentDateEnd}')")->count('id'); + $list[] = ['date' => $date, 'total' => $total]; + } + } + return $list; + } + + public function edit($code, $data) + { + if (!$code) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $code, 'deleted' => 0])->field('id', true)->find(); + if (!$task) { + throw new \Exception('该任务在回收站中无法编辑', 1); + } + if (isset($data['description']) && $data['description'] == '


') { + $data['description'] = ""; + } + $result = self::update($data, ['code' => $code]); + $member = getCurrentMember(); + $type = 'name'; + if (isset($data['name'])) { + $type = 'name'; + } + if (isset($data['description'])) { + $type = 'content'; + if (!$data['description']) { + $type = 'clearContent'; + } + } + if (isset($data['pri'])) { + $type = 'pri'; + } + if (isset($data['end_time'])) { + $type = 'setEndTime'; + if (!$data['end_time']) { + $type = 'clearEndTime'; + } + } + self::taskHook($member['code'], $code, $type); + //TODO 任务动态 + return $result; + } + + public function taskSources($code) + { + if (!$code) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $code])->field('id', true)->find(); + if (!$task) { + throw new \Exception('该任务不存在', 2); + } + $sources = SourceLink::where(['link_code' => $code, 'link_type' => 'task'])->field('id', true)->order('id desc')->select()->toArray(); + if ($sources) { + foreach ($sources as &$source) { + $source = SourceLink::getSourceDetail($source['code']); + } + } + return $sources; + } + + /** + * @param $code + * @param bool $like + * @return bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function like($code, $like = true) + { + if (!$code) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $code, 'deleted' => 0])->field('id', true)->find(); + if (!$task) { + throw new \Exception('该任务在回收站中不能点赞', 1); + } + if ($like) { + $result = self::where(['code' => $code])->setInc('like'); + } else { + $result = self::where(['code' => $code])->setDec('like');; + } + $member = getCurrentMember(); + TaskLike::likeTask($code, $member['code'], $like); + return $result; + } + + /** + * @param $code + * @param bool $star + * @return bool + * @throws \think\Exception + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function star($code, $star = true) + { + if (!$code) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $code, 'deleted' => 0])->field('id', true)->find(); + if (!$task) { + throw new \Exception('该任务在回收站中不能收藏', 1); + } + if ($star) { + $result = self::where(['code' => $code])->setInc('star'); + } else { + $result = self::where(['code' => $code])->setDec('star');; + } + $member = getCurrentMember(); + Collection::starTask($code, $member['code'], $star); + return $result; + } + + /** + * 创建任务 + * @param $stageCode + * @param $projectCode + * @param $name + * @param $memberCode + * @param string $assignTo + * @param string $parentCode + * @return Task + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function createTask($stageCode, $projectCode, $name, $memberCode, $assignTo = '', $parentCode = '') + { + if (!$name) { + throw new \Exception('请填写任务标题', 1); + } + $stage = TaskStages::where(['code' => $stageCode])->field('id')->find(); + if (!$stage) { + throw new \Exception('该任务列表无效', 2); + } + $project = Project::where(['code' => $projectCode, 'deleted' => 0])->field('id')->find(); + if (!$project) { + throw new \Exception('该任务已失效', 3); + } + if ($parentCode) { + $parentTask = self::where(['code' => $parentCode])->find(); + if (!$parentTask) { + throw new \Exception('父任务无效', 5); + } + if ($parentTask['deleted']) { + throw new \Exception('父任务在回收站中无法编辑', 6); + } + } + if ($assignTo) { + $assignMember = Member::where(['code' => $assignTo])->field('id')->find(); + if (!$assignMember) { + throw new \Exception('任务执行人有误', 4); + } + } + + Db::startTrans(); + try { + $taskTitles = explode("\n", $name); + foreach ($taskTitles as $taskTitle) { + if (!trim($taskTitle)) { + continue; + } + $maxNum = self::where(['project_code' => $projectCode])->max('id_num'); + if (!$maxNum) { + $maxNum = 0; + } + $data = [ + 'create_time' => nowTime(), + 'code' => createUniqueCode('task'), + 'create_by' => $memberCode, + 'assign_to' => $assignTo, + 'id_num' => $maxNum + 1, + 'project_code' => $projectCode, + 'pcode' => $parentCode, + 'stage_code' => $stageCode, + 'name' => trim($taskTitle), + ]; + $result = self::create($data); +// self::update(['sort' => $result['id']], ['id' => $result['id']]); + self::taskHook($memberCode, $data['code'], 'create'); + if ($parentCode) { + self::taskHook($memberCode, $parentCode, 'createChild', '', '', 0, '', '', ['taskName' => trim($taskTitle)]); + } + $isExecutor = 0; + $logType = 'inviteMember'; + if ($assignTo) { + if ($memberCode == $assignTo) { + $isExecutor = 1; + $logType = 'claim'; + } +// Task::taskHook($memberCode, $data['code'], $logType, $assignTo); + TaskMember::inviteMember($assignTo, $data['code'], 1, $isExecutor); + } + if (!$assignTo || !$isExecutor) { + TaskMember::inviteMember($memberCode, $data['code'], 0, 1); + } + } + //todo 添加任务动态 + + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage()); + } + return $result; + } + + public function taskDone($taskCode, $done) + { + if (!$taskCode) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $taskCode])->find(); + if (!$task) { + throw new \Exception('任务已失效', 2); + } + if ($task['deleted']) { + throw new \Exception('任务在回收站中无法进行编辑', 3); + } + Db::startTrans(); + try { + $result = self::update(['done' => $done], ['code' => $taskCode]); + //todo 添加任务动态,编辑权限检测 + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage()); + } + $member = getCurrentMember(); + $done ? $type = 'done' : $type = 'redo'; + self::taskHook($member['code'], $taskCode, $type); + if ($task['pcode']) { + $done ? $type = 'doneChild' : $type = 'redoChild'; + self::taskHook($member['code'], $task['pcode'], $type); + } + return $result; + } + + /** + * 指派任务 + * @param $taskCode + * @param $executorCode + * @return TaskMember|bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function assignTask($taskCode, $executorCode) + { + if (!$taskCode) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $taskCode])->find(); + if (!$task) { + throw new \Exception('任务已失效', 2); + } + if ($task['deleted']) { + throw new \Exception('任务在回收站中无法进行指派', 3); + } + Db::startTrans(); + try { + $result = TaskMember::inviteMember($executorCode, $taskCode, 1); + //todo 添加任务动态,编辑权限检测 + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage()); + } + return $result; + } + + /** + * @param $taskCode + * @param $comment + * @return ProjectLog + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function createComment($taskCode, $comment) + { + if (!$taskCode) { + throw new \Exception('请选择任务', 1); + } + $task = self::where(['code' => $taskCode])->find(); + if (!$task) { + throw new \Exception('任务已失效', 2); + } + $data = [ + 'member_code' => getCurrentMember()['code'], + 'task_code' => $taskCode, + 'code' => createUniqueCode('taskLog'), + 'create_time' => nowTime(), + 'is_comment' => 1, + 'content' => $comment, + 'type' => 'comment' + ]; + return ProjectLog::create($data); + } + + /** + * 任务排序 + * @param $stageCode string 移到的任务列表code + * @param $codes array 经过排序的任务code列表 + * @return bool + */ + public function sort($stageCode, $codes) + { + if (!$codes) { + return false; + } + if ($codes) { + foreach ($codes as $key => $code) { + self::update(['sort' => $key, 'stage_code' => $stageCode], ['code' => $code]); + } + return true; + } + return false; + } + + public function getMemberTasks($memberCode = '', $done = 0, $page = 1, $pageSize = 10) + { + if (!$memberCode) { + $memberCode = getCurrentMember()['code']; + } + if ($page < 1) { + $page = 1; + } + $offset = ($page - 1) * $page; + $limit = $pageSize; + $prefix = config('database.prefix'); + $sql = "select *,t.id as id,t.name as name,t.code as code from {$prefix}task as t join {$prefix}project as p on t.project_code = p.code where t.done = {$done} and t.deleted = 0 and t.assign_to = '{$memberCode}' and p.deleted = 0 order by t.id desc"; + $total = Db::query($sql); + $total = count($total); + $sql .= " limit {$offset},{$limit}"; + $list = Db::query($sql); + return ['list' => $list, 'total' => $total]; + } + + /** + * 批量放入回收站 + * @param $stageCode + * @return Task + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recycleBatch($stageCode) + { + $stage = TaskStages::where(['code' => $stageCode])->find(); + if (!$stage) { + throw new \Exception('任务列表不存在', 1); + } + $where = ['stage_code' => $stageCode, 'deleted' => 0]; + $taskCodes = self::where($where)->column('code'); + $memberCode = getCurrentMember()['code']; + if ($taskCodes) { + foreach ($taskCodes as $taskCode) { + self::taskHook($memberCode, $taskCode, 'recycle'); + } + } + $result = self::update(['deleted' => 1, 'deleted_time' => nowTime()], $where); + return $result; + } + + /** + * 放入回收站 + * @param $code + * @return Project + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recycle($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('任务不存在', 1); + } + if ($info['deleted']) { + throw new \Exception('任务已在回收站', 2); + } + $result = self::update(['deleted' => 1, 'deleted_time' => nowTime()], ['code' => $code]); + self::taskHook(getCurrentMember()['code'], $code, 'recycle'); + return $result; + } + + /** + * 恢复任务 + * @param $code + * @return Project + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function recovery($code) + { + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('任务不存在', 1); + } + if (!$info['deleted']) { + throw new \Exception('任务已恢复', 2); + } + $result = self::update(['deleted' => 0], ['code' => $code]); + self::taskHook(getCurrentMember()['code'], $code, 'recovery'); + return $result; + } + + public function del($code) + { + //权限判断 + $info = self::where(['code' => $code])->find(); + if (!$info) { + throw new \Exception('任务不存在', 1); + } + Db::startTrans(); + try { + self::where(['code' => $code])->delete(); + self::where(['pcode' => $code])->delete(); + TaskMember::where(['task_code' => $code])->delete(); + TaskLike::where(['task_code' => $code])->delete(); + ProjectLog::where(['source_code' => $code, 'action_type' => 'task'])->delete(); + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + throw new \Exception($e->getMessage()); + } + return true; + } + + + public function getPriTextAttr($value, $data) + { + if (!isset($data['pri'])) { + $data['pri'] = 0; + } + $status = [0 => '普通', 1 => '紧急', 2 => '非常紧急']; + return $status[$data['pri']]; + } + + public function getChildCountAttr($value, $data) + { + $childTasks = []; + if (isset($data['code'])) { + $childTaskCount = self::where(['pcode' => $data['code'], 'deleted' => 0])->count('id'); + $childTasks[] = $childTaskCount; + $childTaskCount = self::where(['pcode' => $data['code'], 'deleted' => 0, 'done' => 1])->count('id'); + $childTasks[] = $childTaskCount; + } + return $childTasks; + } + + public function getHasCommentAttr($value, $data) + { + $comment = 0; + if (isset($data['code'])) { + $comment = ProjectLog::where(['source_code' => $data['code'], 'type' => 'task', 'is_comment' => 1])->count('id'); + } + return $comment; + } + + public function getHasSourceAttr($value, $data) + { + $sources = 0; + if (isset($data['code'])) { + $sources = SourceLink::where(['link_code' => $data['code'], 'link_type' => 'task'])->count('id'); + } + return $sources; + } + + public function getLikedAttr($value, $data) + { + $like = 0; + if (isset($data['code'])) { + $member = getCurrentMember(); + $taskLike = TaskLike::where(['task_code' => $data['code'], 'member_code' => $member['code']])->find(); + if ($taskLike) { + $like = 1; + } + } + return $like; + } + + public function getStaredAttr($value, $data) + { + $stared = 0; + if (isset($data['code'])) { + $member = getCurrentMember(); + $taskStar = Collection::where(['source_code' => $data['code'], 'type' => 'task', 'member_code' => $member['code']])->find(); + if ($taskStar) { + $stared = 1; + } + } + return $stared; + } + + /** 任务变动钩子 + * @param $memberCode + * @param $taskCode + * @param string $type + * @param string $toMemberCode + * @param int $isComment + * @param string $remark + * @param string $content + * @param string $fileCode + * @param array $data + * @param string $tag + */ + public static function taskHook($memberCode, $taskCode, $type = 'create', $toMemberCode = '', $isComment = 0, $remark = '', $content = '', $fileCode = '', $data = [], $tag = 'task') + { + $data = ['memberCode' => $memberCode, 'taskCode' => $taskCode, 'remark' => $remark, 'type' => $type, 'content' => $content, 'isComment' => $isComment, 'toMemberCode' => $toMemberCode, 'fileCode' => $fileCode, 'data' => $data, 'tag' => $tag]; + Hook::listen($tag, $data); + + } +} diff --git a/application/common/Model/TaskLike.php b/application/common/Model/TaskLike.php new file mode 100644 index 0000000..34ca30b --- /dev/null +++ b/application/common/Model/TaskLike.php @@ -0,0 +1,40 @@ + $code, 'member_code' => $memberCode])->find(); + if ($like && !$liked) { + $data = [ + 'create_time' => nowTime(), + 'create_by' => $memberCode, + 'task_code' => $code, + 'member_code' => $memberCode, + ]; + return self::create($data); + } + if (!$like) { + return self::where(['task_code' => $code, 'member_code' => $memberCode])->delete(); + } + } + +} diff --git a/application/common/Model/TaskMember.php b/application/common/Model/TaskMember.php new file mode 100644 index 0000000..9b81de7 --- /dev/null +++ b/application/common/Model/TaskMember.php @@ -0,0 +1,148 @@ + $taskCode, 'deleted' => 0])->find(); + if (!$task) { + throw new \Exception('该任务已失效', 1); + } + $currentMember = getCurrentMember(); + $taskExecutor = self::where(['is_executor' => 1, 'task_code' => $taskCode])->find(); //原执行者 + + self::update(['is_executor' => 0], ['task_code' => $taskCode]); + if ($memberCode) { + $hasJoined = self::where(['member_code' => $memberCode, 'task_code' => $taskCode])->find(); + if ($hasJoined) { + Task::update(['assign_to' => $memberCode], ['code' => $taskCode]); + self::update(['is_executor' => 1], ['task_code' => $taskCode, 'member_code' => $memberCode]); + $logType = 'assign'; + if ($memberCode == $currentMember['code']) { + $logType = 'claim'; + } + Task::taskHook($currentMember['code'], $taskCode, $logType, $memberCode); +// throw new \Exception('该成员已参与任务', 2); + return true; + } + } + if (!$memberCode) { + //不指派执行人 + Task::update(['assign_to' => $memberCode], ['code' => $taskCode]); + if (!$fromCreate) { + if ($taskExecutor) { + Task::taskHook($currentMember['code'], $taskCode, 'removeExecutor', $taskExecutor['member_code']); + } + } + return true; + } + $data = [ + 'member_code' => $memberCode, + 'task_code' => $taskCode, + 'is_executor' => $isExecutor, + 'is_owner' => $isOwner, + 'join_time' => nowTime() + ]; + //todo 添加任务动态 + $result = self::create($data); + + if ($isExecutor) { + Task::update(['assign_to' => $memberCode], ['code' => $taskCode]); + if ($memberCode == $currentMember['code']) { + Task::taskHook($currentMember['code'], $taskCode, 'claim'); + } else { + Task::taskHook($currentMember['code'], $taskCode, 'assign', $memberCode); + } + } + if ($memberCode) { + $projectModel = new ProjectMember(); + $projectModel->inviteMember($memberCode, $task['project_code']); + } + return $result; + } + + /** + * 批量邀请成员 + * @param $memberCodes + * @param $taskCode + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function inviteMemberBatch($memberCodes, $taskCode) + { + $currentMember = getCurrentMember(); + if (!$memberCodes) { + return false; + } + $task = Task::where(['code' => $taskCode, 'deleted' => 0])->find(); + if (!$task) { + throw new \Exception('该任务已失效', 1); + } + $isAll = false; + if (in_array('all', $memberCodes)) { //全部项目成员 + $memberCodes = ProjectMember::where(['project_code' => $task['project_code']])->column('member_code'); + $isAll = true; + } + if ($memberCodes) { + Db::startTrans(); + try { + $ownerCode = self::where(['is_owner' => 1, 'task_code' => $taskCode])->column('member_code'); + foreach ($memberCodes as $memberCode) { + if ($ownerCode == $memberCode) { + //创建者不能被移除 + continue; + } + $hasJoined = self::where(['member_code' => $memberCode, 'task_code' => $taskCode])->find(); + if ($hasJoined) { + if (!$isAll) { + if ($hasJoined['is_executor']) { + Task::update(['assign_to' => ''], ['code' => $taskCode]); + Task::taskHook($currentMember['code'], $taskCode, 'removeExecutor', $memberCode); + } + self::where(['task_code' => $taskCode, 'member_code' => $memberCode])->delete(); + Task::taskHook($currentMember['code'], $taskCode, 'removeMember', $memberCode); + } + } else { + $data = [ + 'member_code' => $memberCode, + 'task_code' => $taskCode, + 'is_executor' => 0, + 'join_time' => nowTime() + ]; + self::create($data); + Task::taskHook($currentMember['code'], $taskCode, 'inviteMember', $memberCode); + } + } + Db::commit(); + } catch (\Exception $e) { + Db::rollback(); + $this->error($e->getMessage(), $e->getCode());; + } + } + } +} diff --git a/application/common/Model/TaskStages.php b/application/common/Model/TaskStages.php new file mode 100644 index 0000000..9fb16c7 --- /dev/null +++ b/application/common/Model/TaskStages.php @@ -0,0 +1,122 @@ + $stageCode, 'pcode' => '', 'deleted' => $deleted])->order('sort asc,id asc')->field('id', true)->select(); + if ($list) { + foreach ($list as &$task) { + $task['executor'] = Member::where(['code' => $task['assign_to']])->field('name,avatar')->find(); + } + } + return $list; + } + + /** + * @param $name + * @param $projectCode + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function createStage($name, $projectCode) + { + if (!$name) { + throw new \Exception('请填写列表名称', 1); + } + $project = Project::where(['code' => $projectCode, 'deleted' => 0])->field('id')->find(); + if (!$project) { + throw new \Exception('该项目已失效', 3); + } + $data = [ + 'create_time' => nowTime(), + 'code' => createUniqueCode('taskStages'), + 'project_code' => $projectCode, + 'name' => trim($name), + ]; + $result = self::create($data)->toArray(); + self::update(['sort' => $result['id']], ['id' => $result['id']]); + if ($result) { + unset($result['id']); + $result['tasksLoading'] = false; //任务加载状态 + $result['fixedCreator'] = false; //添加任务按钮定位 + $result['showTaskCard'] = false; //是否显示创建卡片 + $result['tasks'] = []; + } + //todo 添加项目动态 + return $result; + } + + /** + * 列表排序(交换两个列表的sort) + * @param $preCode string 前一个移动的列表 + * @param $nextCode string 后一个移动的列表 + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function sort($preCode, $nextCode) + { + $preStage = self::where(['code' => $preCode])->field('sort')->find(); + $nextStage = self::where(['code' => $nextCode])->field('sort')->find(); + if ($preCode == $nextCode) { + return false; + } + if ($preStage !== false && $preStage !== false) { + self::update(['sort' => $nextStage['sort']], ['code' => $preCode]); + self::update(['sort' => $preStage['sort']], ['code' => $nextCode]); + return true; + } + return false; + } + + /** + * 删除列表 + * @param $code + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function deleteStage($code) + { + $stage = self::where(['code' => $code])->field('id')->find(); + if (!$stage) { + throw new \Exception('该列表不存在', 1); + } + $info = Task::where(['stage_code' => $code, 'deleted' => 0])->find(); + if ($info) { + throw new \Exception('请先清空此列表上的任务,然后再删除这个列表', 2); + } + $result = self::destroy(['code' => $code]); + if (!$result) { + throw new \Exception('删除失败', 3); + } + return $result; + } +} diff --git a/application/common/Model/TaskStagesTemplate.php b/application/common/Model/TaskStagesTemplate.php new file mode 100644 index 0000000..ce81df7 --- /dev/null +++ b/application/common/Model/TaskStagesTemplate.php @@ -0,0 +1,84 @@ + nowTime(), + 'code' => createUniqueCode('taskStagesTemplate'), + 'project_template_code' => $projectTemplateCode, + 'name' => $name, + ]; + $result = self::create($data); + return $result; + } + + /** + * 创建任务列表模板 + * @param $code + * @param $name + * @param int $sort + * @return TaskStagesTemplate + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function createTemplate($code, $name, $sort = 0) + { + $data = [ + 'create_time' => nowTime(), + 'code' => createUniqueCode('taskStagesTemplate'), + 'project_template_code' => $code, + 'sort' => $sort, + 'name' => $name, + ]; + $result = self::create($data); + return $result; + } + + /** + * 删除模板 + * @param $code + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function deleteTemplate($code) + { + $template = self::where(['code' => $code])->field('id')->find(); + if (!$template) { + throw new \Exception('该模板不存在', 1); + } + $result = self::destroy(['code' => $code]); + if (!$result) { + throw new \Exception('删除失败', 2); + } + return $result; + } +} diff --git a/application/common/Plugins/GateWayWorker/Events.php b/application/common/Plugins/GateWayWorker/Events.php new file mode 100644 index 0000000..98f528c --- /dev/null +++ b/application/common/Plugins/GateWayWorker/Events.php @@ -0,0 +1,68 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * 用于检测业务代码死循环或者长时间阻塞等问题 + * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload + * 然后观察一段时间workerman.log看是否有process_timeout异常 + */ +//declare(ticks=1); + +use GatewayWorker\Lib\Gateway; + +/** + * 主逻辑 + * 主要是处理 onConnect onMessage onClose 三个方法 + * onConnect 和 onClose 如果不需要可以不用实现并删除 + */ +class Events +{ + /** + * 当客户端连接时触发 + * 如果业务不需此回调可以删除onConnect + * @param int $client_id 连接id + * @throws Exception + */ + public static function onConnect($client_id) + { + // 向当前client_id发送数据 + $data = ['action' => 'connect', 'data' => ['client_id' => $client_id]]; + Gateway::sendToClient($client_id, json_encode($data)); + // 向所有人发送 +// Gateway::sendToAll("$client_id login\r\n"); + } + + /** + * 当客户端发来消息时触发 + * @param int $client_id 连接id + * @param mixed $message 具体消息 + * @throws Exception + */ + public static function onMessage($client_id, $message) + { + // 向所有人发送 + Gateway::sendToAll("$client_id said $message\r\n"); + } + + /** + * 当用户断开连接时触发 + * @param int $client_id 连接id + * @throws Exception + */ + public static function onClose($client_id) + { + // 向所有人发送 + GateWay::sendToAll("$client_id logout\r\n"); + } +} diff --git a/application/common/Plugins/GateWayWorker/composer.json b/application/common/Plugins/GateWayWorker/composer.json new file mode 100644 index 0000000..b72e169 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/composer.json @@ -0,0 +1,11 @@ +{ + "name" : "workerman/gateway-worker-for-win-demo", + "keywords": ["distributed","communication"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/gateway-worker-for-win" : ">=3.0.0", + "workerman/gateway-worker" : ">=3.0.0" + + } +} diff --git a/application/common/Plugins/GateWayWorker/composer.lock b/application/common/Plugins/GateWayWorker/composer.lock new file mode 100644 index 0000000..da1f9e3 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/composer.lock @@ -0,0 +1,175 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "230ece272f676c5e3a976d24a1029942", + "packages": [ + { + "name": "workerman/gateway-worker", + "version": "v3.0.12", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker.git", + "reference": "c206ec41e21f092055d1ddd3ee296895fc004cb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/c206ec41e21f092055d1ddd3ee296895fc004cb5", + "reference": "c206ec41e21f092055d1ddd3ee296895fc004cb5", + "shasum": "" + }, + "require": { + "workerman/workerman": ">=3.1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ], + "time": "2018-08-21T06:17:30+00:00" + }, + { + "name": "workerman/gateway-worker-for-win", + "version": "v3.0.7", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker-for-win.git", + "reference": "ea75b5d581db1762e9928f5729200a1abcaba496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker-for-win/zipball/ea75b5d581db1762e9928f5729200a1abcaba496", + "reference": "ea75b5d581db1762e9928f5729200a1abcaba496", + "shasum": "" + }, + "require": { + "workerman/workerman-for-win": ">=3.1.8" + }, + "type": "library", + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ], + "time": "2017-06-26T14:51:29+00:00" + }, + { + "name": "workerman/workerman", + "version": "v3.5.15", + "source": { + "type": "git", + "url": "https://github.com/walkor/Workerman.git", + "reference": "6df60271e514201a17a96acb8ea16936000444cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/Workerman/zipball/6df60271e514201a17a96acb8ea16936000444cb", + "reference": "6df60271e514201a17a96acb8ea16936000444cb", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "type": "library", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "time": "2018-09-20T09:11:43+00:00" + }, + { + "name": "workerman/workerman-for-win", + "version": "v3.5.1", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman-for-win.git", + "reference": "cbaae3193e4567fd9cfc8099931c63d9b12174ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman-for-win/zipball/cbaae3193e4567fd9cfc8099931c63d9b12174ee", + "reference": "cbaae3193e4567fd9cfc8099931c63d9b12174ee", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "type": "project", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ], + "time": "2017-08-28T10:05:00+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [] +} diff --git a/application/common/Plugins/GateWayWorker/start_businessworker.php b/application/common/Plugins/GateWayWorker/start_businessworker.php new file mode 100644 index 0000000..fb5bb16 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/start_businessworker.php @@ -0,0 +1,37 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +use Workerman\Worker; +use Workerman\WebServer; +use GatewayWorker\Gateway; +use GatewayWorker\BusinessWorker; +use Workerman\Autoloader; +require_once __DIR__ . '/vendor/autoload.php'; + +// 自动加载类 + +// bussinessWorker 进程 +$worker = new BusinessWorker(); +// worker名称 +$worker->name = 'YourAppBusinessWorker'; +// bussinessWorker进程数量 +$worker->count = 4; +// 服务注册地址 +$worker->registerAddress = '192.168.0.159:2346'; + +// 如果不是在根目录启动,则运行runAll方法 +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} + diff --git a/application/common/Plugins/GateWayWorker/start_gateway.php b/application/common/Plugins/GateWayWorker/start_gateway.php new file mode 100644 index 0000000..6794615 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/start_gateway.php @@ -0,0 +1,85 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +use Workerman\Worker; +use Workerman\WebServer; +use GatewayWorker\Gateway; +use GatewayWorker\BusinessWorker; +use Workerman\Autoloader; +require_once __DIR__ . '/vendor/autoload.php'; + +$ssl = false; +$context = array(); +if ($ssl) { + // 证书最好是申请的证书 + $context = array( + // 更多ssl选项请参考手册 http://php.net/manual/zh/context.ssl.php + 'ssl' => array( + // 请使用绝对路径 + 'local_cert' => '/www/wwwroot/pms/server.pem', // 也可以是crt文件 + 'local_pk' => '/www/wwwroot/pms/server.key', + 'verify_peer' => false, + 'allow_self_signed' => true, //如果是自签名证书需要开启此选项 + ) + ); +} + +// gateway 进程,这里使用Text协议,可以用telnet测试 +$gateway = new Gateway("websocket://192.168.0.159:2345", $context); + +if ($ssl) { + // 开启SSL,websocket+SSL 即wss + $gateway->transport = 'ssl'; +} + +// gateway名称,status方便查看 +$gateway->name = 'YourAppGateway'; +// gateway进程数 +$gateway->count = 4; +// 本机ip,分布式部署时使用内网ip +$gateway->lanIp = '127.0.0.1'; +// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000 +// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口 +$gateway->startPort = 2900; +// 服务注册地址 +$gateway->registerAddress = '192.168.0.159:2346'; + +// 心跳间隔 +//$gateway->pingInterval = 10; +// 心跳数据 +//$gateway->pingData = '{"type":"ping"}'; + +/* +// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调 +$gateway->onConnect = function($connection) +{ + $connection->onWebSocketConnect = function($connection , $http_header) + { + // 可以在这里判断连接来源是否合法,不合法就关掉连接 + // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接 + if($_SERVER['HTTP_ORIGIN'] != 'http://kedou.workerman.net') + { + $connection->close(); + } + // onWebSocketConnect 里面$_GET $_SERVER是可用的 + // var_dump($_GET, $_SERVER); + }; +}; +*/ + +// 如果不是在根目录启动,则运行runAll方法 +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} + diff --git a/application/common/Plugins/GateWayWorker/start_register.php b/application/common/Plugins/GateWayWorker/start_register.php new file mode 100644 index 0000000..d7d05b8 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/start_register.php @@ -0,0 +1,29 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +use Workerman\Worker; +use GatewayWorker\Register; +require_once __DIR__ . '/vendor/autoload.php'; + + +// 自动加载类 + +// register 必须是text协议 +$register = new Register('http://192.168.0.159:2346'); + +// 如果不是在根目录启动,则运行runAll方法 +if(!defined('GLOBAL_START')) +{ + Worker::runAll(); +} + diff --git a/application/common/Plugins/GateWayWorker/vendor/autoload.php b/application/common/Plugins/GateWayWorker/vendor/autoload.php new file mode 100644 index 0000000..738b12e --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/autoload.php @@ -0,0 +1,7 @@ + + * Jordi Boggiano + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Composer\Autoload; + +/** + * ClassLoader implements a PSR-0, PSR-4 and classmap class loader. + * + * $loader = new \Composer\Autoload\ClassLoader(); + * + * // register classes with namespaces + * $loader->add('Symfony\Component', __DIR__.'/component'); + * $loader->add('Symfony', __DIR__.'/framework'); + * + * // activate the autoloader + * $loader->register(); + * + * // to enable searching the include path (eg. for PEAR packages) + * $loader->setUseIncludePath(true); + * + * In this example, if you try to use a class in the Symfony\Component + * namespace or one of its children (Symfony\Component\Console for instance), + * the autoloader will first look for the class under the component/ + * directory, and it will then fallback to the framework/ directory if not + * found before giving up. + * + * This class is loosely based on the Symfony UniversalClassLoader. + * + * @author Fabien Potencier + * @author Jordi Boggiano + * @see http://www.php-fig.org/psr/psr-0/ + * @see http://www.php-fig.org/psr/psr-4/ + */ +class ClassLoader +{ + // PSR-4 + private $prefixLengthsPsr4 = array(); + private $prefixDirsPsr4 = array(); + private $fallbackDirsPsr4 = array(); + + // PSR-0 + private $prefixesPsr0 = array(); + private $fallbackDirsPsr0 = array(); + + private $useIncludePath = false; + private $classMap = array(); + private $classMapAuthoritative = false; + private $missingClasses = array(); + private $apcuPrefix; + + public function getPrefixes() + { + if (!empty($this->prefixesPsr0)) { + return call_user_func_array('array_merge', $this->prefixesPsr0); + } + + return array(); + } + + public function getPrefixesPsr4() + { + return $this->prefixDirsPsr4; + } + + public function getFallbackDirs() + { + return $this->fallbackDirsPsr0; + } + + public function getFallbackDirsPsr4() + { + return $this->fallbackDirsPsr4; + } + + public function getClassMap() + { + return $this->classMap; + } + + /** + * @param array $classMap Class to filename map + */ + public function addClassMap(array $classMap) + { + if ($this->classMap) { + $this->classMap = array_merge($this->classMap, $classMap); + } else { + $this->classMap = $classMap; + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, either + * appending or prepending to the ones previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 root directories + * @param bool $prepend Whether to prepend the directories + */ + public function add($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + $this->fallbackDirsPsr0 = array_merge( + (array) $paths, + $this->fallbackDirsPsr0 + ); + } else { + $this->fallbackDirsPsr0 = array_merge( + $this->fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset($this->prefixesPsr0[$first][$prefix])) { + $this->prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + if ($prepend) { + $this->prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + $this->prefixesPsr0[$first][$prefix] + ); + } else { + $this->prefixesPsr0[$first][$prefix] = array_merge( + $this->prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, either + * appending or prepending to the ones previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * @param bool $prepend Whether to prepend the directories + * + * @throws \InvalidArgumentException + */ + public function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + $this->fallbackDirsPsr4 = array_merge( + (array) $paths, + $this->fallbackDirsPsr4 + ); + } else { + $this->fallbackDirsPsr4 = array_merge( + $this->fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset($this->prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + $this->prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + $this->prefixDirsPsr4[$prefix] = array_merge( + $this->prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + /** + * Registers a set of PSR-0 directories for a given prefix, + * replacing any others previously set for this prefix. + * + * @param string $prefix The prefix + * @param array|string $paths The PSR-0 base directories + */ + public function set($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr0 = (array) $paths; + } else { + $this->prefixesPsr0[$prefix[0]][$prefix] = (array) $paths; + } + } + + /** + * Registers a set of PSR-4 directories for a given namespace, + * replacing any others previously set for this namespace. + * + * @param string $prefix The prefix/namespace, with trailing '\\' + * @param array|string $paths The PSR-4 base directories + * + * @throws \InvalidArgumentException + */ + public function setPsr4($prefix, $paths) + { + if (!$prefix) { + $this->fallbackDirsPsr4 = (array) $paths; + } else { + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + $this->prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + $this->prefixDirsPsr4[$prefix] = (array) $paths; + } + } + + /** + * Turns on searching the include path for class files. + * + * @param bool $useIncludePath + */ + public function setUseIncludePath($useIncludePath) + { + $this->useIncludePath = $useIncludePath; + } + + /** + * Can be used to check if the autoloader uses the include path to check + * for classes. + * + * @return bool + */ + public function getUseIncludePath() + { + return $this->useIncludePath; + } + + /** + * Turns off searching the prefix and fallback directories for classes + * that have not been registered with the class map. + * + * @param bool $classMapAuthoritative + */ + public function setClassMapAuthoritative($classMapAuthoritative) + { + $this->classMapAuthoritative = $classMapAuthoritative; + } + + /** + * Should class lookup fail if not found in the current class map? + * + * @return bool + */ + public function isClassMapAuthoritative() + { + return $this->classMapAuthoritative; + } + + /** + * APCu prefix to use to cache found/not-found classes, if the extension is enabled. + * + * @param string|null $apcuPrefix + */ + public function setApcuPrefix($apcuPrefix) + { + $this->apcuPrefix = function_exists('apcu_fetch') && filter_var(ini_get('apc.enabled'), FILTER_VALIDATE_BOOLEAN) ? $apcuPrefix : null; + } + + /** + * The APCu prefix in use, or null if APCu caching is not enabled. + * + * @return string|null + */ + public function getApcuPrefix() + { + return $this->apcuPrefix; + } + + /** + * Registers this instance as an autoloader. + * + * @param bool $prepend Whether to prepend the autoloader or not + */ + public function register($prepend = false) + { + spl_autoload_register(array($this, 'loadClass'), true, $prepend); + } + + /** + * Unregisters this instance as an autoloader. + */ + public function unregister() + { + spl_autoload_unregister(array($this, 'loadClass')); + } + + /** + * Loads the given class or interface. + * + * @param string $class The name of the class + * @return bool|null True if loaded, null otherwise + */ + public function loadClass($class) + { + if ($file = $this->findFile($class)) { + includeFile($file); + + return true; + } + } + + /** + * Finds the path to the file where the class is defined. + * + * @param string $class The name of the class + * + * @return string|false The path if found, false otherwise + */ + public function findFile($class) + { + // class map lookup + if (isset($this->classMap[$class])) { + return $this->classMap[$class]; + } + if ($this->classMapAuthoritative || isset($this->missingClasses[$class])) { + return false; + } + if (null !== $this->apcuPrefix) { + $file = apcu_fetch($this->apcuPrefix.$class, $hit); + if ($hit) { + return $file; + } + } + + $file = $this->findFileWithExtension($class, '.php'); + + // Search for Hack files if we are running on HHVM + if (false === $file && defined('HHVM_VERSION')) { + $file = $this->findFileWithExtension($class, '.hh'); + } + + if (null !== $this->apcuPrefix) { + apcu_add($this->apcuPrefix.$class, $file); + } + + if (false === $file) { + // Remember that this class does not exist. + $this->missingClasses[$class] = true; + } + + return $file; + } + + private function findFileWithExtension($class, $ext) + { + // PSR-4 lookup + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext; + + $first = $class[0]; + if (isset($this->prefixLengthsPsr4[$first])) { + $subPath = $class; + while (false !== $lastPos = strrpos($subPath, '\\')) { + $subPath = substr($subPath, 0, $lastPos); + $search = $subPath . '\\'; + if (isset($this->prefixDirsPsr4[$search])) { + $pathEnd = DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $lastPos + 1); + foreach ($this->prefixDirsPsr4[$search] as $dir) { + if (file_exists($file = $dir . $pathEnd)) { + return $file; + } + } + } + } + } + + // PSR-4 fallback dirs + foreach ($this->fallbackDirsPsr4 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // PSR-0 lookup + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . $ext; + } + + if (isset($this->prefixesPsr0[$first])) { + foreach ($this->prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // PSR-0 fallback dirs + foreach ($this->fallbackDirsPsr0 as $dir) { + if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + // PSR-0 include paths. + if ($this->useIncludePath && $file = stream_resolve_include_path($logicalPathPsr0)) { + return $file; + } + + return false; + } +} + +/** + * Scope isolated include. + * + * Prevents access to $this/self from included files. + */ +function includeFile($file) +{ + include $file; +} diff --git a/application/common/Plugins/GateWayWorker/vendor/composer/LICENSE b/application/common/Plugins/GateWayWorker/vendor/composer/LICENSE new file mode 100644 index 0000000..f27399a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/composer/LICENSE @@ -0,0 +1,21 @@ + +Copyright (c) Nils Adermann, Jordi Boggiano + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + diff --git a/application/common/Plugins/GateWayWorker/vendor/composer/autoload_classmap.php b/application/common/Plugins/GateWayWorker/vendor/composer/autoload_classmap.php new file mode 100644 index 0000000..7a91153 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/composer/autoload_classmap.php @@ -0,0 +1,9 @@ + array($vendorDir . '/workerman/workerman', $vendorDir . '/workerman/workerman-for-win'), + 'GatewayWorker\\' => array($vendorDir . '/workerman/gateway-worker/src', $vendorDir . '/workerman/gateway-worker-for-win/src'), +); diff --git a/application/common/Plugins/GateWayWorker/vendor/composer/autoload_real.php b/application/common/Plugins/GateWayWorker/vendor/composer/autoload_real.php new file mode 100644 index 0000000..0005179 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/composer/autoload_real.php @@ -0,0 +1,52 @@ += 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded()); + if ($useStaticLoader) { + require_once __DIR__ . '/autoload_static.php'; + + call_user_func(\Composer\Autoload\ComposerStaticInit5472d435d8e6cb539c167a0dead78fd7::getInitializer($loader)); + } else { + $map = require __DIR__ . '/autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + $loader->set($namespace, $path); + } + + $map = require __DIR__ . '/autoload_psr4.php'; + foreach ($map as $namespace => $path) { + $loader->setPsr4($namespace, $path); + } + + $classMap = require __DIR__ . '/autoload_classmap.php'; + if ($classMap) { + $loader->addClassMap($classMap); + } + } + + $loader->register(true); + + return $loader; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/composer/autoload_static.php b/application/common/Plugins/GateWayWorker/vendor/composer/autoload_static.php new file mode 100644 index 0000000..8db5f8d --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/composer/autoload_static.php @@ -0,0 +1,41 @@ + + array ( + 'Workerman\\' => 10, + ), + 'G' => + array ( + 'GatewayWorker\\' => 14, + ), + ); + + public static $prefixDirsPsr4 = array ( + 'Workerman\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/workerman', + 1 => __DIR__ . '/..' . '/workerman/workerman-for-win', + ), + 'GatewayWorker\\' => + array ( + 0 => __DIR__ . '/..' . '/workerman/gateway-worker/src', + 1 => __DIR__ . '/..' . '/workerman/gateway-worker-for-win/src', + ), + ); + + public static function getInitializer(ClassLoader $loader) + { + return \Closure::bind(function () use ($loader) { + $loader->prefixLengthsPsr4 = ComposerStaticInit5472d435d8e6cb539c167a0dead78fd7::$prefixLengthsPsr4; + $loader->prefixDirsPsr4 = ComposerStaticInit5472d435d8e6cb539c167a0dead78fd7::$prefixDirsPsr4; + + }, null, ClassLoader::class); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/composer/installed.json b/application/common/Plugins/GateWayWorker/vendor/composer/installed.json new file mode 100644 index 0000000..d156725 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/composer/installed.json @@ -0,0 +1,167 @@ +[ + { + "name": "workerman/gateway-worker", + "version": "v3.0.12", + "version_normalized": "3.0.12.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker.git", + "reference": "c206ec41e21f092055d1ddd3ee296895fc004cb5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/c206ec41e21f092055d1ddd3ee296895fc004cb5", + "reference": "c206ec41e21f092055d1ddd3ee296895fc004cb5", + "shasum": "" + }, + "require": { + "workerman/workerman": ">=3.1.8" + }, + "time": "2018-08-21T06:17:30+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ] + }, + { + "name": "workerman/gateway-worker-for-win", + "version": "v3.0.7", + "version_normalized": "3.0.7.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/GatewayWorker-for-win.git", + "reference": "ea75b5d581db1762e9928f5729200a1abcaba496" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/GatewayWorker-for-win/zipball/ea75b5d581db1762e9928f5729200a1abcaba496", + "reference": "ea75b5d581db1762e9928f5729200a1abcaba496", + "shasum": "" + }, + "require": { + "workerman/workerman-for-win": ">=3.1.8" + }, + "time": "2017-06-26T14:51:29+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "GatewayWorker\\": "./src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "homepage": "http://www.workerman.net", + "keywords": [ + "communication", + "distributed" + ] + }, + { + "name": "workerman/workerman", + "version": "v3.5.15", + "version_normalized": "3.5.15.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/Workerman.git", + "reference": "6df60271e514201a17a96acb8ea16936000444cb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/Workerman/zipball/6df60271e514201a17a96acb8ea16936000444cb", + "reference": "6df60271e514201a17a96acb8ea16936000444cb", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "time": "2018-09-20T09:11:43+00:00", + "type": "library", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ] + }, + { + "name": "workerman/workerman-for-win", + "version": "v3.5.1", + "version_normalized": "3.5.1.0", + "source": { + "type": "git", + "url": "https://github.com/walkor/workerman-for-win.git", + "reference": "cbaae3193e4567fd9cfc8099931c63d9b12174ee" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/walkor/workerman-for-win/zipball/cbaae3193e4567fd9cfc8099931c63d9b12174ee", + "reference": "cbaae3193e4567fd9cfc8099931c63d9b12174ee", + "shasum": "" + }, + "require": { + "php": ">=5.3" + }, + "time": "2017-08-28T10:05:00+00:00", + "type": "project", + "installation-source": "dist", + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "homepage": "http://www.workerman.net", + "keywords": [ + "asynchronous", + "event-loop" + ] + } +] diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/MIT-LICENSE.txt b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/README.md b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/README.md new file mode 100644 index 0000000..a32efc7 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/README.md @@ -0,0 +1,30 @@ +GatewayWorker windows 版本 +================= + +GatewayWorker基于[Workerman](https://github.com/walkor/Workerman)开发的一个项目框架,用于快速开发长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等。 + +GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接,并转发客户端的数据给Worker进程处理;Worker进程负责处理实际的业务逻辑,并将结果推送给对应的客户端。Gateway服务和Worker服务可以分开部署在不同的服务器上,实现分布式集群。 + +GatewayWorker提供非常方便的API,可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器,也可以定时推送数据。 + +下载安装 +===== +本仓库只是GatewayWorker的核心仓库, +完整的版本[点击这里下载](http://www.workerman.net/download/GatewayWorker-for-win.zip) + + +手册 +======= +http://www.workerman.net/gatewaydoc/ + +使用GatewayWorker-for-win开发的项目 +======= +## [tadpole](http://kedou.workerman.net/) +[Live demo](http://kedou.workerman.net/) +[Source code](https://github.com/walkor/workerman) +![workerman-todpole](http://www.workerman.net/img/workerman-todpole.png) + +## [chat room](http://chat.workerman.net/) +[Live demo](http://chat.workerman.net/) +[Source code](https://github.com/walkor/workerman-chat) +![workerman-chat](http://www.workerman.net/img/workerman-chat.png) diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/composer.json b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/composer.json new file mode 100644 index 0000000..e9387cb --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/composer.json @@ -0,0 +1,12 @@ +{ + "name" : "workerman/gateway-worker-for-win", + "keywords": ["distributed","communication"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/workerman-for-win" : ">=3.1.8" + }, + "autoload": { + "psr-4": {"GatewayWorker\\": "./src"} + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/BusinessWorker.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/BusinessWorker.php new file mode 100644 index 0000000..22d8f5d --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/BusinessWorker.php @@ -0,0 +1,546 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use Workerman\Connection\TcpConnection; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\AsyncTcpConnection; +use GatewayWorker\Protocols\GatewayProtocol; +use GatewayWorker\Lib\Context; + +/** + * + * BusinessWorker 用于处理Gateway转发来的数据 + * + * @author walkor + * + */ +class BusinessWorker extends Worker +{ + /** + * 保存与 gateway 的连接 connection 对象 + * + * @var array + */ + public $gatewayConnections = array(); + + /** + * 注册中心地址 + * + * @var string + */ + public $registerAddress = '127.0.0.1:1236'; + + /** + * 事件处理类,默认是 Event 类 + * + * @var string + */ + public $eventHandler = 'Events'; + + /** + * 业务超时时间,可用来定位程序卡在哪里 + * + * @var int + */ + public $processTimeout = 30; + + /** + * 业务超时时间,可用来定位程序卡在哪里 + * + * @var callable + */ + public $processTimeoutHandler = '\\Workerman\\Worker::log'; + + /** + * 秘钥 + * + * @var string + */ + public $secretKey = ''; + + /** + * businessWorker进程将消息转发给gateway进程的发送缓冲区大小 + * + * @var int + */ + public $sendToGatewayBufferSize = 10240000; + + /** + * 保存用户设置的 worker 启动回调 + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * 保存用户设置的 workerReload 回调 + * + * @var callback + */ + protected $_onWorkerReload = null; + + /** + * 保存用户设置的 workerStop 回调 + * + * @var callback + */ + protected $_onWorkerStop= null; + + /** + * 到注册中心的连接 + * + * @var AsyncTcpConnection + */ + protected $_registerConnection = null; + + /** + * 处于连接状态的 gateway 通讯地址 + * + * @var array + */ + protected $_connectingGatewayAddresses = array(); + + /** + * 所有 geteway 内部通讯地址 + * + * @var array + */ + protected $_gatewayAddresses = array(); + + /** + * 等待连接个 gateway 地址 + * + * @var array + */ + protected $_waitingConnectGatewayAddresses = array(); + + /** + * Event::onConnect 回调 + * + * @var callback + */ + protected $_eventOnConnect = null; + + /** + * Event::onMessage 回调 + * + * @var callback + */ + protected $_eventOnMessage = null; + + /** + * Event::onClose 回调 + * + * @var callback + */ + protected $_eventOnClose = null; + + /** + * SESSION 版本缓存 + * + * @var array + */ + protected $_sessionVersion = array(); + + /** + * 用于保持长连接的心跳时间间隔 + * + * @var int + */ + const PERSISTENCE_CONNECTION_PING_INTERVAL = 25; + + /** + * 构造函数 + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', $context_option = array()) + { + parent::__construct($socket_name, $context_option); + $backrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backrace[0]['file']); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->_onWorkerReload = $this->onWorkerReload; + $this->_onWorkerStop = $this->onWorkerStop; + $this->onWorkerStop = array($this, 'onWorkerStop'); + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onWorkerReload = array($this, 'onWorkerReload'); + parent::run(); + } + + /** + * 当进程启动时一些初始化工作 + * + * @return void + */ + protected function onWorkerStart() + { + if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); + } + $this->connectToRegister(); + \GatewayWorker\Lib\Gateway::setBusinessWorker($this); + \GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey; + if ($this->_onWorkerStart) { + call_user_func($this->_onWorkerStart, $this); + } + + if (is_callable($this->eventHandler . '::onWorkerStart')) { + call_user_func($this->eventHandler . '::onWorkerStart', $this); + } + + if (function_exists('pcntl_signal')) { + // 业务超时信号处理 + pcntl_signal(SIGALRM, array($this, 'timeoutHandler'), false); + } else { + $this->processTimeout = 0; + } + + // 设置回调 + if (is_callable($this->eventHandler . '::onConnect')) { + $this->_eventOnConnect = $this->eventHandler . '::onConnect'; + } + + if (is_callable($this->eventHandler . '::onMessage')) { + $this->_eventOnMessage = $this->eventHandler . '::onMessage'; + } else { + echo "Waring: {$this->eventHandler}::onMessage is not callable\n"; + } + + if (is_callable($this->eventHandler . '::onClose')) { + $this->_eventOnClose = $this->eventHandler . '::onClose'; + } + + // 如果Register服务器不在本地服务器,则需要保持心跳 + if (strpos($this->registerAddress, '127.0.0.1') !== 0) { + Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingRegister')); + } + } + + /** + * onWorkerReload 回调 + * + * @param Worker $worker + */ + protected function onWorkerReload($worker) + { + // 防止进程立刻退出 + $worker->reloadable = false; + // 延迟 0.05 秒退出,避免 BusinessWorker 瞬间全部退出导致没有可用的 BusinessWorker 进程 + Timer::add(0.05, array('Workerman\Worker', 'stopAll')); + // 执行用户定义的 onWorkerReload 回调 + if ($this->_onWorkerReload) { + call_user_func($this->_onWorkerReload, $this); + } + } + + /** + * 当进程关闭时一些清理工作 + * + * @return void + */ + protected function onWorkerStop() + { + if ($this->_onWorkerStop) { + call_user_func($this->_onWorkerStop, $this); + } + if (is_callable($this->eventHandler . '::onWorkerStop')) { + call_user_func($this->eventHandler . '::onWorkerStop', $this); + } + } + + /** + * 连接服务注册中心 + * + * @return void + */ + public function connectToRegister() + { + $this->_registerConnection = new AsyncTcpConnection("text://{$this->registerAddress}"); + $this->_registerConnection->send('{"event":"worker_connect","secret_key":"' . $this->secretKey . '"}'); + $this->_registerConnection->onClose = array($this, 'onRegisterConnectionClose'); + $this->_registerConnection->onMessage = array($this, 'onRegisterConnectionMessage'); + $this->_registerConnection->connect(); + } + + /** + * 与注册中心连接关闭时,定时重连 + * + * @return void + */ + public function onRegisterConnectionClose() + { + Timer::add(1, array($this, 'connectToRegister'), null, false); + } + + /** + * 当注册中心发来消息时 + * + * @return void + */ + public function onRegisterConnectionMessage($register_connection, $data) + { + $data = json_decode($data, true); + if (!isset($data['event'])) { + echo "Received bad data from Register\n"; + return; + } + $event = $data['event']; + switch ($event) { + case 'broadcast_addresses': + if (!is_array($data['addresses'])) { + echo "Received bad data from Register. Addresses empty\n"; + return; + } + $addresses = $data['addresses']; + $this->_gatewayAddresses = array(); + foreach ($addresses as $addr) { + $this->_gatewayAddresses[$addr] = $addr; + } + $this->checkGatewayConnections($addresses); + break; + default: + echo "Receive bad event:$event from Register.\n"; + } + } + + /** + * 当 gateway 转发来数据时 + * + * @param TcpConnection $connection + * @param mixed $data + */ + public function onGatewayMessage($connection, $data) + { + $cmd = $data['cmd']; + if ($cmd === GatewayProtocol::CMD_PING) { + return; + } + // 上下文数据 + Context::$client_ip = $data['client_ip']; + Context::$client_port = $data['client_port']; + Context::$local_ip = $data['local_ip']; + Context::$local_port = $data['local_port']; + Context::$connection_id = $data['connection_id']; + Context::$client_id = Context::addressToClientId($data['local_ip'], $data['local_port'], + $data['connection_id']); + // $_SERVER 变量 + $_SERVER = array( + 'REMOTE_ADDR' => long2ip($data['client_ip']), + 'REMOTE_PORT' => $data['client_port'], + 'GATEWAY_ADDR' => long2ip($data['local_ip']), + 'GATEWAY_PORT' => $data['gateway_port'], + 'GATEWAY_CLIENT_ID' => Context::$client_id, + ); + // 检查session版本,如果是过期的session数据则拉取最新的数据 + if ($cmd !== GatewayProtocol::CMD_ON_CLOSE && isset($this->_sessionVersion[Context::$client_id]) && $this->_sessionVersion[Context::$client_id] !== crc32($data['ext_data'])) { + $_SESSION = Context::$old_session = \GatewayWorker\Lib\Gateway::getSession(Context::$client_id); + } else { + if (!isset($this->_sessionVersion[Context::$client_id])) { + $this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']); + } + // 尝试解析 session + if ($data['ext_data'] != '') { + Context::$old_session = $_SESSION = Context::sessionDecode($data['ext_data']); + } else { + Context::$old_session = $_SESSION = null; + } + } + + if ($this->processTimeout) { + pcntl_alarm($this->processTimeout); + } + // 尝试执行 Event::onConnection、Event::onMessage、Event::onClose + switch ($cmd) { + case GatewayProtocol::CMD_ON_CONNECTION: + if ($this->_eventOnConnect) { + call_user_func($this->_eventOnConnect, Context::$client_id); + } + break; + case GatewayProtocol::CMD_ON_MESSAGE: + if ($this->_eventOnMessage) { + call_user_func($this->_eventOnMessage, Context::$client_id, $data['body']); + } + break; + case GatewayProtocol::CMD_ON_CLOSE: + unset($this->_sessionVersion[Context::$client_id]); + if ($this->_eventOnClose) { + call_user_func($this->_eventOnClose, Context::$client_id); + } + break; + } + if ($this->processTimeout) { + pcntl_alarm(0); + } + + // session 必须是数组 + if ($_SESSION !== null && !is_array($_SESSION)) { + throw new \Exception('$_SESSION must be an array. But $_SESSION=' . var_export($_SESSION, true) . ' is not array.'); + } + + // 判断 session 是否被更改 + if ($_SESSION !== Context::$old_session && $cmd !== GatewayProtocol::CMD_ON_CLOSE) { + $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : ''; + \GatewayWorker\Lib\Gateway::setSocketSession(Context::$client_id, $session_str_now); + $this->_sessionVersion[Context::$client_id] = crc32($session_str_now); + } + + Context::clear(); + } + + /** + * 当与 Gateway 的连接断开时触发 + * + * @param TcpConnection $connection + * @return void + */ + public function onGatewayClose($connection) + { + $addr = $connection->remoteAddress; + unset($this->gatewayConnections[$addr], $this->_connectingGatewayAddresses[$addr]); + if (isset($this->_gatewayAddresses[$addr]) && !isset($this->_waitingConnectGatewayAddresses[$addr])) { + Timer::add(1, array($this, 'tryToConnectGateway'), array($addr), false); + $this->_waitingConnectGatewayAddresses[$addr] = $addr; + } + } + + /** + * 尝试连接 Gateway 内部通讯地址 + * + * @param string $addr + */ + public function tryToConnectGateway($addr) + { + if (!isset($this->gatewayConnections[$addr]) && !isset($this->_connectingGatewayAddresses[$addr]) && isset($this->_gatewayAddresses[$addr])) { + $gateway_connection = new AsyncTcpConnection("GatewayProtocol://$addr"); + $gateway_connection->remoteAddress = $addr; + $gateway_connection->onConnect = array($this, 'onConnectGateway'); + $gateway_connection->onMessage = array($this, 'onGatewayMessage'); + $gateway_connection->onClose = array($this, 'onGatewayClose'); + $gateway_connection->onError = array($this, 'onGatewayError'); + $gateway_connection->maxSendBufferSize = $this->sendToGatewayBufferSize; + if (TcpConnection::$defaultMaxSendBufferSize == $gateway_connection->maxSendBufferSize) { + $gateway_connection->maxSendBufferSize = 50 * 1024 * 1024; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_WORKER_CONNECT; + $gateway_data['body'] = json_encode(array( + 'worker_key' =>"{$this->name}:{$this->id}", + 'secret_key' => $this->secretKey, + )); + $gateway_connection->send($gateway_data); + $gateway_connection->connect(); + $this->_connectingGatewayAddresses[$addr] = $addr; + } + unset($this->_waitingConnectGatewayAddresses[$addr]); + } + + /** + * 检查 gateway 的通信端口是否都已经连 + * 如果有未连接的端口,则尝试连接 + * + * @param array $addresses_list + */ + public function checkGatewayConnections($addresses_list) + { + if (empty($addresses_list)) { + return; + } + foreach ($addresses_list as $addr) { + if (!isset($this->_waitingConnectGatewayAddresses[$addr])) { + $this->tryToConnectGateway($addr); + } + } + } + + /** + * 当连接上 gateway 的通讯端口时触发 + * 将连接 connection 对象保存起来 + * + * @param TcpConnection $connection + * @return void + */ + public function onConnectGateway($connection) + { + $this->gatewayConnections[$connection->remoteAddress] = $connection; + unset($this->_connectingGatewayAddresses[$connection->remoteAddress], $this->_waitingConnectGatewayAddresses[$connection->remoteAddress]); + } + + /** + * 当与 gateway 的连接出现错误时触发 + * + * @param TcpConnection $connection + * @param int $error_no + * @param string $error_msg + */ + public function onGatewayError($connection, $error_no, $error_msg) + { + echo "GatewayConnection Error : $error_no ,$error_msg\n"; + } + + /** + * 获取所有 Gateway 内部通讯地址 + * + * @return array + */ + public function getAllGatewayAddresses() + { + return $this->_gatewayAddresses; + } + + /** + * 业务超时回调 + * + * @param int $signal + * @throws \Exception + */ + public function timeoutHandler($signal) + { + switch ($signal) { + // 超时时钟 + case SIGALRM: + // 超时异常 + $e = new \Exception("process_timeout", 506); + $trace_str = $e->getTraceAsString(); + // 去掉第一行timeoutHandler的调用栈 + $trace_str = $e->getMessage() . ":\n" . substr($trace_str, strpos($trace_str, "\n") + 1) . "\n"; + // 开发者没有设置超时处理函数,或者超时处理函数返回空则执行退出 + if (!$this->processTimeoutHandler || !call_user_func($this->processTimeoutHandler, $trace_str, $e)) { + Worker::stopAll(); + } + break; + } + } + + /** + * 向 Register 发送心跳,用来保持长连接 + */ + public function pingRegister() + { + if ($this->_registerConnection) { + $this->_registerConnection->send('{"event":"ping"}'); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Gateway.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Gateway.php new file mode 100644 index 0000000..b130081 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Gateway.php @@ -0,0 +1,916 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use GatewayWorker\Lib\Context; + +use Workerman\Connection\TcpConnection; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Autoloader; +use Workerman\Connection\AsyncTcpConnection; +use GatewayWorker\Protocols\GatewayProtocol; + +/** + * + * Gateway,基于Worker 开发 + * 用于转发客户端的数据给Worker处理,以及转发Worker的数据给客户端 + * + * @author walkor + * + */ +class Gateway extends Worker +{ + /** + * 版本 + * + * @var string + */ + const VERSION = '3.0.7'; + + /** + * 本机 IP + * 单机部署默认 127.0.0.1,如果是分布式部署,需要设置成本机 IP + * + * @var string + */ + public $lanIp = '127.0.0.1'; + + /** + * 本机端口 + * + * @var string + */ + public $lanPort = 0; + + /** + * gateway 内部通讯起始端口,每个 gateway 实例应该都不同,步长1000 + * + * @var int + */ + public $startPort = 2000; + + /** + * 注册服务地址,用于注册 Gateway BusinessWorker,使之能够通讯 + * + * @var string + */ + public $registerAddress = '127.0.0.1:1236'; + + /** + * 是否可以平滑重启,gateway 不能平滑重启,否则会导致连接断开 + * + * @var bool + */ + public $reloadable = false; + + /** + * 心跳时间间隔 + * + * @var int + */ + public $pingInterval = 0; + + /** + * $pingNotResponseLimit * $pingInterval 时间内,客户端未发送任何数据,断开客户端连接 + * + * @var int + */ + public $pingNotResponseLimit = 0; + + /** + * 服务端向客户端发送的心跳数据 + * + * @var string + */ + public $pingData = ''; + + /** + * 秘钥 + * + * @var string + */ + public $secretKey = ''; + + /** + * 路由函数 + * + * @var callback + */ + public $router = null; + + + /** + * gateway进程转发给businessWorker进程的发送缓冲区大小 + * + * @var int + */ + public $sendToWorkerBufferSize = 10240000; + + /** + * gateway进程将数据发给客户端时每个客户端发送缓冲区大小 + * + * @var int + */ + public $sendToClientBufferSize = 1024000; + + /** + * 协议加速 + * + * @var bool + */ + public $protocolAccelerate = false; + + /** + * 保存客户端的所有 connection 对象 + * + * @var array + */ + protected $_clientConnections = array(); + + /** + * uid 到 connection 的映射,一对多关系 + */ + protected $_uidConnections = array(); + + /** + * group 到 connection 的映射,一对多关系 + * + * @var array + */ + protected $_groupConnections = array(); + + /** + * 保存所有 worker 的内部连接的 connection 对象 + * + * @var array + */ + protected $_workerConnections = array(); + + /** + * gateway 内部监听 worker 内部连接的 worker + * + * @var Worker + */ + protected $_innerTcpWorker = null; + + /** + * 当 worker 启动时 + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * 当有客户端连接时 + * + * @var callback + */ + protected $_onConnect = null; + + /** + * 当客户端发来消息时 + * + * @var callback + */ + protected $_onMessage = null; + + /** + * 当客户端连接关闭时 + * + * @var callback + */ + protected $_onClose = null; + + /** + * 当 worker 停止时 + * + * @var callback + */ + protected $_onWorkerStop = null; + + /** + * 进程启动时间 + * + * @var int + */ + protected $_startTime = 0; + + /** + * gateway 监听的端口 + * + * @var int + */ + protected $_gatewayPort = 0; + + /** + * 到注册中心的连接 + * + * @var AsyncTcpConnection + */ + protected $_registerConnection = null; + + /** + * connectionId 记录器 + * @var int + */ + protected static $_connectionIdRecorder = 0; + + /** + * 用于保持长连接的心跳时间间隔 + * + * @var int + */ + const PERSISTENCE_CONNECTION_PING_INTERVAL = 25; + + /** + * 构造函数 + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, $context_option = array()) + { + parent::__construct($socket_name, $context_option); + $this->_gatewayPort = substr(strrchr($socket_name,':'),1); + $this->router = array("\\GatewayWorker\\Gateway", 'routerBind'); + + $backrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backrace[0]['file']); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // 保存用户的回调,当对应的事件发生时触发 + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + // 保存用户的回调,当对应的事件发生时触发 + $this->_onConnect = $this->onConnect; + $this->onConnect = array($this, 'onClientConnect'); + + // onMessage禁止用户设置回调 + $this->onMessage = array($this, 'onClientMessage'); + + // 保存用户的回调,当对应的事件发生时触发 + $this->_onClose = $this->onClose; + $this->onClose = array($this, 'onClientClose'); + // 保存用户的回调,当对应的事件发生时触发 + $this->_onWorkerStop = $this->onWorkerStop; + $this->onWorkerStop = array($this, 'onWorkerStop'); + + // 记录进程启动的时间 + $this->_startTime = time(); + // 运行父方法 + parent::run(); + } + + /** + * 当客户端发来数据时,转发给worker处理 + * + * @param TcpConnection $connection + * @param mixed $data + */ + public function onClientMessage($connection, $data) + { + $connection->pingNotResponseCount = -1; + $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data); + } + + /** + * 当客户端连接上来时,初始化一些客户端的数据 + * 包括全局唯一的client_id、初始化session等 + * + * @param TcpConnection $connection + */ + public function onClientConnect($connection) + { + $connection->id = self::generateConnectionId(); + // 保存该连接的内部通讯的数据包报头,避免每次重新初始化 + $connection->gatewayHeader = array( + 'local_ip' => ip2long($this->lanIp), + 'local_port' => $this->lanPort, + 'client_ip' => ip2long($connection->getRemoteIp()), + 'client_port' => $connection->getRemotePort(), + 'gateway_port' => $this->_gatewayPort, + 'connection_id' => $connection->id, + 'flag' => 0, + ); + // 连接的 session + $connection->session = ''; + // 该连接的心跳参数 + $connection->pingNotResponseCount = -1; + // 该链接发送缓冲区大小 + $connection->maxSendBufferSize = $this->sendToClientBufferSize; + // 保存客户端连接 connection 对象 + $this->_clientConnections[$connection->id] = $connection; + + // 如果用户有自定义 onConnect 回调,则执行 + if ($this->_onConnect) { + call_user_func($this->_onConnect, $connection); + } + + $this->sendToWorker(GatewayProtocol::CMD_ON_CONNECTION, $connection); + } + + /** + * 生成connection id + * @return int + */ + protected function generateConnectionId() + { + $max_unsigned_int = 4294967295; + if (self::$_connectionIdRecorder >= $max_unsigned_int) { + self::$_connectionIdRecorder = 0; + } + while(++self::$_connectionIdRecorder <= $max_unsigned_int) { + if(!isset($this->_clientConnections[self::$_connectionIdRecorder])) { + break; + } + } + return self::$_connectionIdRecorder; + } + + /** + * 发送数据给 worker 进程 + * + * @param int $cmd + * @param TcpConnection $connection + * @param mixed $body + * @return bool + */ + protected function sendToWorker($cmd, $connection, $body = '') + { + $gateway_data = $connection->gatewayHeader; + $gateway_data['cmd'] = $cmd; + $gateway_data['body'] = $body; + $gateway_data['ext_data'] = $connection->session; + if ($this->_workerConnections) { + // 调用路由函数,选择一个worker把请求转发给它 + /** @var TcpConnection $worker_connection */ + $worker_connection = call_user_func($this->router, $this->_workerConnections, $connection, $cmd, $body); + if (false === $worker_connection->send($gateway_data)) { + $msg = "SendBufferToWorker fail. May be the send buffer are overflow. See http://wiki.workerman.net/Error2 for detail"; + $this->log($msg); + return false; + } + } // 没有可用的 worker + else { + // gateway 启动后 1-2 秒内 SendBufferToWorker fail 是正常现象,因为与 worker 的连接还没建立起来, + // 所以不记录日志,只是关闭连接 + $time_diff = 2; + if (time() - $this->_startTime >= $time_diff) { + $msg = 'SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://wiki.workerman.net/Error3 for detail'; + $this->log($msg); + } + $connection->destroy(); + return false; + } + return true; + } + + /** + * 随机路由,返回 worker connection 对象 + * + * @param array $worker_connections + * @param TcpConnection $client_connection + * @param int $cmd + * @param mixed $buffer + * @return TcpConnection + */ + public static function routerRand($worker_connections, $client_connection, $cmd, $buffer) + { + return $worker_connections[array_rand($worker_connections)]; + } + + /** + * client_id 与 worker 绑定 + * + * @param array $worker_connections + * @param TcpConnection $client_connection + * @param int $cmd + * @param mixed $buffer + * @return TcpConnection + */ + public static function routerBind($worker_connections, $client_connection, $cmd, $buffer) + { + if (!isset($client_connection->businessworker_address) || !isset($worker_connections[$client_connection->businessworker_address])) { + $client_connection->businessworker_address = array_rand($worker_connections); + } + return $worker_connections[$client_connection->businessworker_address]; + } + + /** + * 当客户端关闭时 + * + * @param TcpConnection $connection + */ + public function onClientClose($connection) + { + // 尝试通知 worker,触发 Event::onClose + $this->sendToWorker(GatewayProtocol::CMD_ON_CLOSE, $connection); + unset($this->_clientConnections[$connection->id]); + // 清理 uid 数据 + if (!empty($connection->uid)) { + $uid = $connection->uid; + unset($this->_uidConnections[$uid][$connection->id]); + if (empty($this->_uidConnections[$uid])) { + unset($this->_uidConnections[$uid]); + } + } + // 清理 group 数据 + if (!empty($connection->groups)) { + foreach ($connection->groups as $group) { + unset($this->_groupConnections[$group][$connection->id]); + if (empty($this->_groupConnections[$group])) { + unset($this->_groupConnections[$group]); + } + } + } + // 触发 onClose + if ($this->_onClose) { + call_user_func($this->_onClose, $connection); + } + } + + /** + * 当 Gateway 启动的时候触发的回调函数 + * + * @return void + */ + public function onWorkerStart() + { + // 分配一个内部通讯端口 + $this->lanPort = $this->startPort + $this->id; + + // 如果有设置心跳,则定时执行 + if ($this->pingInterval > 0) { + $timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval / 2 : $this->pingInterval; + Timer::add($timer_interval, array($this, 'ping')); + } + + // 如果BusinessWorker ip不是127.0.0.1,则需要加gateway到BusinessWorker的心跳 + if ($this->lanIp !== '127.0.0.1') { + Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingBusinessWorker')); + } + + // 如果 Register 服务器不在本地服务器,则需要保持心跳 + if (strpos($this->registerAddress, '127.0.0.1') !== 0) { + Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingRegister')); + } + + if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); + } + + // 初始化 gateway 内部的监听,用于监听 worker 的连接已经连接上发来的数据 + $this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}"); + $this->_innerTcpWorker->listen(); + + // 重新设置自动加载根目录 + Autoloader::setRootPath($this->_autoloadRootPath); + + // 设置内部监听的相关回调 + $this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage'); + + $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect'); + $this->_innerTcpWorker->onClose = array($this, 'onWorkerClose'); + + // 注册 gateway 的内部通讯地址,worker 去连这个地址,以便 gateway 与 worker 之间建立起 TCP 长连接 + $this->registerAddress(); + + if ($this->_onWorkerStart) { + call_user_func($this->_onWorkerStart, $this); + } + } + + + /** + * 当 worker 通过内部通讯端口连接到 gateway 时 + * + * @param TcpConnection $connection + */ + public function onWorkerConnect($connection) + { + $connection->maxSendBufferSize = $this->sendToWorkerBufferSize; + $connection->authorized = $this->secretKey ? false : true; + } + + /** + * 当 worker 发来数据时 + * + * @param TcpConnection $connection + * @param mixed $data + * @throws \Exception + */ + public function onWorkerMessage($connection, $data) + { + $cmd = $data['cmd']; + if (empty($connection->authorized) && $cmd !== GatewayProtocol::CMD_WORKER_CONNECT && $cmd !== GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT) { + self::log("Unauthorized request from " . $connection->getRemoteIp() . ":" . $connection->getRemotePort()); + return $connection->close(); + } + switch ($cmd) { + // BusinessWorker连接Gateway + case GatewayProtocol::CMD_WORKER_CONNECT: + $worker_info = json_decode($data['body'], true); + if ($worker_info['secret_key'] !== $this->secretKey) { + self::log("Gateway: Worker key does not match ".var_export($this->secretKey, true)." !== ". var_export($this->secretKey)); + return $connection->close(); + } + $key = $connection->getRemoteIp() . ':' . $worker_info['worker_key']; + // 在一台服务器上businessWorker->name不能相同 + if (isset($this->_workerConnections[$key])) { + self::log("Gateway: Worker->name conflict. Key:{$key}"); + $connection->close(); + return; + } + $connection->key = $key; + $this->_workerConnections[$key] = $connection; + $connection->authorized = true; + return; + // GatewayClient连接Gateway + case GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT: + $worker_info = json_decode($data['body'], true); + if ($worker_info['secret_key'] !== $this->secretKey) { + self::log("Gateway: GatewayClient key does not match ".var_export($this->secretKey, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $connection->authorized = true; + return; + // 向某客户端发送数据,Gateway::sendToClient($client_id, $message); + case GatewayProtocol::CMD_SEND_TO_ONE: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->send($data['body']); + } + return; + // 踢出用户,Gateway::closeClient($client_id, $message); + case GatewayProtocol::CMD_KICK: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->close($data['body']); + } + return; + // 立即销毁用户连接, Gateway::destroyClient($client_id); + case GatewayProtocol::CMD_DESTROY: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->destroy(); + } + return; + // 广播, Gateway::sendToAll($message, $client_id_array) + case GatewayProtocol::CMD_SEND_TO_ALL: + $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE); + $body = $data['body']; + if (!$raw && $this->protocolAccelerate && $this->protocol) { + $body = $this->preEncodeForClient($body); + $raw = true; + } + $ext_data = $data['ext_data'] ? json_decode($data['ext_data'], true) : ''; + // $client_id_array 不为空时,只广播给 $client_id_array 指定的客户端 + if (isset($ext_data['connections'])) { + foreach ($ext_data['connections'] as $connection_id) { + if (isset($this->_clientConnections[$connection_id])) { + $this->_clientConnections[$connection_id]->send($body, $raw); + } + } + } // $client_id_array 为空时,广播给所有在线客户端 + else { + $exclude_connection_id = !empty($ext_data['exclude']) ? $ext_data['exclude'] : null; + foreach ($this->_clientConnections as $client_connection) { + if (!isset($exclude_connection_id[$client_connection->id])) { + $client_connection->send($body, $raw); + } + } + } + return; + // 重新赋值 session + case GatewayProtocol::CMD_SET_SESSION: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; + } + return; + // session合并 + case GatewayProtocol::CMD_UPDATE_SESSION: + if (!isset($this->_clientConnections[$data['connection_id']])) { + return; + } else { + if (!$this->_clientConnections[$data['connection_id']]->session) { + $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; + return; + } + $session = Context::sessionDecode($this->_clientConnections[$data['connection_id']]->session); + $session_for_merge = Context::sessionDecode($data['ext_data']); + $session = $session_for_merge + $session; + $this->_clientConnections[$data['connection_id']]->session = Context::sessionEncode($session); + } + return; + case GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID: + if (!isset($this->_clientConnections[$data['connection_id']])) { + $session = serialize(null); + } else { + if (!$this->_clientConnections[$data['connection_id']]->session) { + $session = serialize(array()); + } else { + $session = $this->_clientConnections[$data['connection_id']]->session; + } + } + $connection->send(pack('N', strlen($session)) . $session, true); + return; + // 获得客户端在线状态 Gateway::getALLClientInfo() + case GatewayProtocol::CMD_GET_ALL_CLIENT_INFO: + $client_info_array = array(); + foreach ($this->_clientConnections as $connection_id => $client_connection) { + $client_info_array[$connection_id] = $client_connection->session; + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 判断某个 client_id 是否在线 Gateway::isOnline($client_id) + case GatewayProtocol::CMD_IS_ONLINE: + $buffer = serialize((int)isset($this->_clientConnections[$data['connection_id']])); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 将 client_id 与 uid 绑定 + case GatewayProtocol::CMD_BIND_UID: + $uid = $data['ext_data']; + if (empty($uid)) { + echo "bindUid(client_id, uid) uid empty, uid=" . var_export($uid, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (isset($client_connection->uid)) { + $current_uid = $client_connection->uid; + unset($this->_uidConnections[$current_uid][$connection_id]); + if (empty($this->_uidConnections[$current_uid])) { + unset($this->_uidConnections[$current_uid]); + } + } + $client_connection->uid = $uid; + $this->_uidConnections[$uid][$connection_id] = $client_connection; + return; + // client_id 与 uid 解绑 Gateway::unbindUid($client_id, $uid); + case GatewayProtocol::CMD_UNBIND_UID: + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (isset($client_connection->uid)) { + $current_uid = $client_connection->uid; + unset($this->_uidConnections[$current_uid][$connection_id]); + if (empty($this->_uidConnections[$current_uid])) { + unset($this->_uidConnections[$current_uid]); + } + $client_connection->uid_info = ''; + $client_connection->uid = null; + } + return; + // 发送数据给 uid Gateway::sendToUid($uid, $msg); + case GatewayProtocol::CMD_SEND_TO_UID: + $uid_array = json_decode($data['ext_data'], true); + foreach ($uid_array as $uid) { + if (!empty($this->_uidConnections[$uid])) { + foreach ($this->_uidConnections[$uid] as $connection) { + /** @var TcpConnection $connection */ + $connection->send($data['body']); + } + } + } + return; + // 将 $client_id 加入用户组 Gateway::joinGroup($client_id, $group); + case GatewayProtocol::CMD_JOIN_GROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "join(group) group empty, group=" . var_export($group, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (!isset($client_connection->groups)) { + $client_connection->groups = array(); + } + $client_connection->groups[$group] = $group; + $this->_groupConnections[$group][$connection_id] = $client_connection; + return; + // 将 $client_id 从某个用户组中移除 Gateway::leaveGroup($client_id, $group); + case GatewayProtocol::CMD_LEAVE_GROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "leave(group) group empty, group=" . var_export($group, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (!isset($client_connection->groups[$group])) { + return; + } + unset($client_connection->groups[$group], $this->_groupConnections[$group][$connection_id]); + return; + // 向某个用户组发送消息 Gateway::sendToGroup($group, $msg); + case GatewayProtocol::CMD_SEND_TO_GROUP: + $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE); + $body = $data['body']; + if (!$raw && $this->protocolAccelerate && $this->protocol) { + $body = $this->preEncodeForClient($body); + $raw = true; + } + $ext_data = json_decode($data['ext_data'], true); + $group_array = $ext_data['group']; + $exclude_connection_id = $ext_data['exclude']; + + foreach ($group_array as $group) { + if (!empty($this->_groupConnections[$group])) { + foreach ($this->_groupConnections[$group] as $connection) { + if(!isset($exclude_connection_id[$connection->id])) + { + /** @var TcpConnection $connection */ + $connection->send($body, $raw); + } + } + } + } + return; + // 获取某用户组成员信息 Gateway::getClientInfoByGroup($group); + case GatewayProtocol::CMD_GET_CLINET_INFO_BY_GROUP: + $group = $data['ext_data']; + if (!isset($this->_groupConnections[$group])) { + $buffer = serialize(array()); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + } + $client_info_array = array(); + foreach ($this->_groupConnections[$group] as $connection_id => $client_connection) { + $client_info_array[$connection_id] = $client_connection->session; + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取用户组成员数 Gateway::getClientCountByGroup($group); + case GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP: + $group = $data['ext_data']; + $count = 0; + if ($group !== '') { + if (isset($this->_groupConnections[$group])) { + $count = count($this->_groupConnections[$group]); + } + } else { + $count = count($this->_clientConnections); + } + $buffer = serialize($count); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取与某个 uid 绑定的所有 client_id Gateway::getClientIdByUid($uid); + case GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID: + $uid = $data['ext_data']; + if (empty($this->_uidConnections[$uid])) { + $buffer = serialize(array()); + } else { + $buffer = serialize(array_keys($this->_uidConnections[$uid])); + } + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + default : + $err_msg = "gateway inner pack err cmd=$cmd"; + throw new \Exception($err_msg); + } + } + + /** + * 当worker连接关闭时 + * + * @param TcpConnection $connection + */ + public function onWorkerClose($connection) + { + // $this->log("{$connection->key} CLOSE INNER_CONNECTION\n"); + if (isset($connection->key)) { + unset($this->_workerConnections[$connection->key]); + } + } + + /** + * 存储当前 Gateway 的内部通信地址 + * + * @return bool + */ + public function registerAddress() + { + $address = $this->lanIp . ':' . $this->lanPort; + $this->_registerConnection = new AsyncTcpConnection("text://{$this->registerAddress}"); + $this->_registerConnection->send('{"event":"gateway_connect", "address":"' . $address . '", "secret_key":"' . $this->secretKey . '"}'); + $this->_registerConnection->onClose = array($this, 'onRegisterConnectionClose'); + $this->_registerConnection->connect(); + } + + public function onRegisterConnectionClose() + { + Timer::add(1, array($this, 'registerAddress'), null, false); + } + + /** + * 心跳逻辑 + * + * @return void + */ + public function ping() + { + $ping_data = $this->pingData ? (string)$this->pingData : null; + $raw = false; + if ($this->protocolAccelerate && $ping_data && $this->protocol) { + $ping_data = $this->preEncodeForClient($ping_data); + $raw = true; + } + // 遍历所有客户端连接 + foreach ($this->_clientConnections as $connection) { + // 上次发送的心跳还没有回复次数大于限定值就断开 + if ($this->pingNotResponseLimit > 0 && + $connection->pingNotResponseCount >= $this->pingNotResponseLimit * 2 + ) { + $connection->destroy(); + continue; + } + // $connection->pingNotResponseCount 为 -1 说明最近客户端有发来消息,则不给客户端发送心跳 + $connection->pingNotResponseCount++; + if ($ping_data) { + if ($connection->pingNotResponseCount === 0 || + ($this->pingNotResponseLimit > 0 && $connection->pingNotResponseCount % 2 === 1) + ) { + continue; + } + $connection->send($ping_data, $raw); + } + } + } + + /** + * 向 BusinessWorker 发送心跳数据,用于保持长连接 + * + * @return void + */ + public function pingBusinessWorker() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_PING; + foreach ($this->_workerConnections as $connection) { + $connection->send($gateway_data); + } + } + + /** + * 向 Register 发送心跳,用来保持长连接 + */ + public function pingRegister() + { + if ($this->_registerConnection) { + $this->_registerConnection->send('{"event":"ping"}'); + } + } + + /** + * @param mixed $data + * + * @return string + */ + protected function preEncodeForClient($data) + { + foreach ($this->_clientConnections as $client_connection) { + return call_user_func(array($client_connection->protocol, 'encode'), $data, $client_connection); + } + } + + /** + * 当 gateway 关闭时触发,清理数据 + * + * @return void + */ + public function onWorkerStop() + { + // 尝试触发用户设置的回调 + if ($this->_onWorkerStop) { + call_user_func($this->_onWorkerStop, $this); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Context.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Context.php new file mode 100644 index 0000000..22ebccb --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Context.php @@ -0,0 +1,136 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Exception; + +/** + * 上下文 包含当前用户 uid, 内部通信 local_ip local_port socket_id,以及客户端 client_ip client_port + */ +class Context +{ + /** + * 内部通讯 id + * + * @var string + */ + public static $local_ip; + + /** + * 内部通讯端口 + * + * @var int + */ + public static $local_port; + + /** + * 客户端 ip + * + * @var string + */ + public static $client_ip; + + /** + * 客户端端口 + * + * @var int + */ + public static $client_port; + + /** + * client_id + * + * @var string + */ + public static $client_id; + + /** + * 连接 connection->id + * + * @var int + */ + public static $connection_id; + + /** + * 旧的session + * + * @var string + */ + public static $old_session; + + /** + * 编码 session + * + * @param mixed $session_data + * @return string + */ + public static function sessionEncode($session_data = '') + { + if ($session_data !== '') { + return serialize($session_data); + } + return ''; + } + + /** + * 解码 session + * + * @param string $session_buffer + * @return mixed + */ + public static function sessionDecode($session_buffer) + { + return unserialize($session_buffer); + } + + /** + * 清除上下文 + * + * @return void + */ + public static function clear() + { + self::$local_ip = self::$local_port = self::$client_ip = self::$client_port = + self::$client_id = self::$connection_id = self::$old_session = null; + } + + /** + * 通讯地址到 client_id 的转换 + * + * @param int $local_ip + * @param int $local_port + * @param int $connection_id + * @return string + */ + public static function addressToClientId($local_ip, $local_port, $connection_id) + { + return bin2hex(pack('NnN', $local_ip, $local_port, $connection_id)); + } + + /** + * client_id 到通讯地址的转换 + * + * @param string $client_id + * @return array + * @throws Exception + */ + public static function clientIdToAddress($client_id) + { + if (strlen($client_id) !== 20) { + echo new Exception("client_id $client_id is invalid"); + return false; + } + return unpack('Nlocal_ip/nlocal_port/Nconnection_id', pack('H*', $client_id)); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Db.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Db.php new file mode 100644 index 0000000..5f7c4d3 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Db.php @@ -0,0 +1,76 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Config\Db as DbConfig; +use Exception; + +/** + * 数据库类 + */ +class Db +{ + /** + * 实例数组 + * + * @var array + */ + protected static $instance = array(); + + /** + * 获取实例 + * + * @param string $config_name + * @return DbConnection + * @throws Exception + */ + public static function instance($config_name) + { + if (!isset(DbConfig::$$config_name)) { + echo "\\Config\\Db::$config_name not set\n"; + throw new Exception("\\Config\\Db::$config_name not set\n"); + } + + if (empty(self::$instance[$config_name])) { + $config = DbConfig::$$config_name; + self::$instance[$config_name] = new DbConnection($config['host'], $config['port'], + $config['user'], $config['password'], $config['dbname']); + } + return self::$instance[$config_name]; + } + + /** + * 关闭数据库实例 + * + * @param string $config_name + */ + public static function close($config_name) + { + if (isset(self::$instance[$config_name])) { + self::$instance[$config_name]->closeConnection(); + self::$instance[$config_name] = null; + } + } + + /** + * 关闭所有数据库实例 + */ + public static function closeAll() + { + foreach (self::$instance as $connection) { + $connection->closeConnection(); + } + self::$instance = array(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/DbConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/DbConnection.php new file mode 100644 index 0000000..df6daff --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/DbConnection.php @@ -0,0 +1,1976 @@ +type = 'SELECT'; + if (!is_array($cols)) { + $cols = array($cols); + } + $this->cols($cols); + return $this; + } + + /** + * 从哪个表删除 + * + * @param string $table + * @return self + */ + public function delete($table) + { + $this->type = 'DELETE'; + $this->table = $this->quoteName($table); + $this->fromRaw($this->quoteName($table)); + return $this; + } + + /** + * 更新哪个表 + * + * @param string $table + * @return self + */ + public function update($table) + { + $this->type = 'UPDATE'; + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 向哪个表插入 + * + * @param string $table + * @return self + */ + public function insert($table) + { + $this->type = 'INSERT'; + $this->table = $this->quoteName($table); + return $this; + } + + /** + * + * 设置 SQL_CALC_FOUND_ROWS 标记. + * + * @param bool $enable + * @return self + */ + public function calcFoundRows($enable = true) + { + $this->setFlag('SQL_CALC_FOUND_ROWS', $enable); + return $this; + } + + /** + * 设置 SQL_CACHE 标记 + * + * @param bool $enable + * @return self + */ + public function cache($enable = true) + { + $this->setFlag('SQL_CACHE', $enable); + return $this; + } + + /** + * 设置 SQL_NO_CACHE 标记 + * + * @param bool $enable + * @return self + */ + public function noCache($enable = true) + { + $this->setFlag('SQL_NO_CACHE', $enable); + return $this; + } + + /** + * 设置 STRAIGHT_JOIN 标记. + * + * @param bool $enable + * @return self + */ + public function straightJoin($enable = true) + { + $this->setFlag('STRAIGHT_JOIN', $enable); + return $this; + } + + /** + * 设置 HIGH_PRIORITY 标记 + * + * @param bool $enable + * @return self + */ + public function highPriority($enable = true) + { + $this->setFlag('HIGH_PRIORITY', $enable); + return $this; + } + + /** + * 设置 SQL_SMALL_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function smallResult($enable = true) + { + $this->setFlag('SQL_SMALL_RESULT', $enable); + return $this; + } + + /** + * 设置 SQL_BIG_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function bigResult($enable = true) + { + $this->setFlag('SQL_BIG_RESULT', $enable); + return $this; + } + + /** + * 设置 SQL_BUFFER_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function bufferResult($enable = true) + { + $this->setFlag('SQL_BUFFER_RESULT', $enable); + return $this; + } + + /** + * 设置 FOR UPDATE 标记 + * + * @param bool $enable + * @return self + */ + public function forUpdate($enable = true) + { + $this->for_update = (bool)$enable; + return $this; + } + + /** + * 设置 DISTINCT 标记 + * + * @param bool $enable + * @return self + */ + public function distinct($enable = true) + { + $this->setFlag('DISTINCT', $enable); + return $this; + } + + /** + * 设置 LOW_PRIORITY 标记 + * + * @param bool $enable + * @return self + */ + public function lowPriority($enable = true) + { + $this->setFlag('LOW_PRIORITY', $enable); + return $this; + } + + /** + * 设置 IGNORE 标记 + * + * @param bool $enable + * @return self + */ + public function ignore($enable = true) + { + $this->setFlag('IGNORE', $enable); + return $this; + } + + /** + * 设置 QUICK 标记 + * + * @param bool $enable + * @return self + */ + public function quick($enable = true) + { + $this->setFlag('QUICK', $enable); + return $this; + } + + /** + * 设置 DELAYED 标记 + * + * @param bool $enable + * @return self + */ + public function delayed($enable = true) + { + $this->setFlag('DELAYED', $enable); + return $this; + } + + /** + * 序列化 + * + * @return string + */ + public function __toString() + { + $union = ''; + if ($this->union) { + $union = implode(' ', $this->union) . ' '; + } + return $union . $this->build(); + } + + /** + * 设置每页多少条记录 + * + * @param int $paging + * @return self + */ + public function setPaging($paging) + { + $this->paging = (int)$paging; + return $this; + } + + /** + * 获取每页多少条记录 + * + * @return int + */ + public function getPaging() + { + return $this->paging; + } + + /** + * 获取绑定在占位符上的值 + */ + public function getBindValues() + { + switch ($this->type) { + case 'SELECT': + return $this->getBindValuesSELECT(); + case 'DELETE': + case 'UPDATE': + case 'INSERT': + return $this->getBindValuesCOMMON(); + default : + throw new Exception("type err"); + } + } + + /** + * 获取绑定在占位符上的值 + * + * @return array + */ + public function getBindValuesSELECT() + { + $bind_values = $this->bind_values; + $i = 1; + foreach ($this->bind_where as $val) { + $bind_values[$i] = $val; + $i++; + } + foreach ($this->bind_having as $val) { + $bind_values[$i] = $val; + $i++; + } + return $bind_values; + } + + /** + * + * SELECT选择哪些列 + * + * @param mixed $key + * @param string $val + * @return void + */ + protected function addColSELECT($key, $val) + { + if (is_string($key)) { + $this->cols[$val] = $key; + } else { + $this->addColWithAlias($val); + } + } + + /** + * SELECT 增加选择的列 + * + * @param string $spec + */ + protected function addColWithAlias($spec) + { + $parts = explode(' ', $spec); + $count = count($parts); + if ($count == 2) { + $this->cols[$parts[1]] = $parts[0]; + } elseif ($count == 3 && strtoupper($parts[1]) == 'AS') { + $this->cols[$parts[2]] = $parts[0]; + } else { + $this->cols[] = $spec; + } + } + + /** + * from 哪个表 + * + * @param string $table + * @return self + */ + public function from($table) + { + return $this->fromRaw($this->quoteName($table)); + } + + /** + * from的表 + * + * @param string $table + * @return self + */ + public function fromRaw($table) + { + $this->from[] = array($table); + $this->from_key++; + return $this; + } + + /** + * + * 子查询 + * + * @param string $table + * @param string $name The alias name for the sub-select. + * @return self + */ + public function fromSubSelect($table, $name) + { + $this->from[] = array("($table) AS " . $this->quoteName($name)); + $this->from_key++; + return $this; + } + + + /** + * 增加 join 语句 + * + * @param string $table + * @param string $cond + * @param string $type + * @return self + * @throws Exception + */ + public function join($table, $cond = null, $type = '') + { + return $this->joinInternal($type, $table, $cond); + } + + /** + * 增加 join 语句 + * + * @param string $join inner, left, natural + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + protected function joinInternal($join, $table, $cond = null) + { + if (!$this->from) { + throw new Exception('Cannot join() without from()'); + } + + $join = strtoupper(ltrim("$join JOIN")); + $table = $this->quoteName($table); + $cond = $this->fixJoinCondition($cond); + $this->from[$this->from_key][] = rtrim("$join $table $cond"); + return $this; + } + + /** + * quote + * + * @param string $cond + * @return string + * + */ + protected function fixJoinCondition($cond) + { + if (!$cond) { + return ''; + } + + $cond = $this->quoteNamesIn($cond); + + if (strtoupper(substr(ltrim($cond), 0, 3)) == 'ON ') { + return $cond; + } + + if (strtoupper(substr(ltrim($cond), 0, 6)) == 'USING ') { + return $cond; + } + + return 'ON ' . $cond; + } + + /** + * inner join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function innerJoin($table, $cond = null) + { + return $this->joinInternal('INNER', $table, $cond); + } + + /** + * left join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function leftJoin($table, $cond = null) + { + return $this->joinInternal('LEFT', $table, $cond); + } + + /** + * right join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function rightJoin($table, $cond = null) + { + return $this->joinInternal('RIGHT', $table, $cond); + } + + /** + * joinSubSelect + * + * @param string $join inner, left, natural + * @param string $spec + * @param string $name sub-select 的别名 + * @param string $cond + * @return self + * @throws Exception + */ + public function joinSubSelect($join, $spec, $name, $cond = null) + { + if (!$this->from) { + throw new \Exception('Cannot join() without from() first.'); + } + + $join = strtoupper(ltrim("$join JOIN")); + $name = $this->quoteName($name); + $cond = $this->fixJoinCondition($cond); + $this->from[$this->from_key][] = rtrim("$join ($spec) AS $name $cond"); + return $this; + } + + /** + * group by 语句 + * + * @param array $cols + * @return self + */ + public function groupBy(array $cols) + { + foreach ($cols as $col) { + $this->group_by[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * having 语句 + * + * @param string $cond + * @return self + */ + public function having($cond) + { + $this->addClauseCondWithBind('having', 'AND', func_get_args()); + return $this; + } + + /** + * or having 语句 + * + * @param string $cond The HAVING condition. + * @return self + */ + public function orHaving($cond) + { + $this->addClauseCondWithBind('having', 'OR', func_get_args()); + return $this; + } + + /** + * 设置每页的记录数量 + * + * @param int $page + * @return self + */ + public function page($page) + { + $this->limit = 0; + $this->offset = 0; + + $page = (int)$page; + if ($page > 0) { + $this->limit = $this->paging; + $this->offset = $this->paging * ($page - 1); + } + return $this; + } + + /** + * union + * + * @return self + */ + public function union() + { + $this->union[] = $this->build() . ' UNION'; + $this->reset(); + return $this; + } + + /** + * unionAll + * + * @return self + */ + public function unionAll() + { + $this->union[] = $this->build() . ' UNION ALL'; + $this->reset(); + return $this; + } + + /** + * 重置 + */ + protected function reset() + { + $this->resetFlags(); + $this->cols = array(); + $this->from = array(); + $this->from_key = -1; + $this->where = array(); + $this->group_by = array(); + $this->having = array(); + $this->order_by = array(); + $this->limit = 0; + $this->offset = 0; + $this->for_update = false; + } + + /** + * 清除所有数据 + */ + protected function resetAll() + { + $this->union = array(); + $this->for_update = false; + $this->cols = array(); + $this->from = array(); + $this->from_key = -1; + $this->group_by = array(); + $this->having = array(); + $this->bind_having = array(); + $this->paging = 10; + $this->bind_values = array(); + $this->where = array(); + $this->bind_where = array(); + $this->order_by = array(); + $this->limit = 0; + $this->offset = 0; + $this->flags = array(); + $this->table = ''; + $this->last_insert_id_names = array(); + $this->col_values = array(); + $this->returning = array(); + $this->parameters = array(); + } + + /** + * 创建 SELECT SQL + * + * @return string + */ + protected function buildSELECT() + { + return 'SELECT' + . $this->buildFlags() + . $this->buildCols() + . $this->buildFrom() + . $this->buildWhere() + . $this->buildGroupBy() + . $this->buildHaving() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildForUpdate(); + } + + /** + * 创建 DELETE SQL + */ + protected function buildDELETE() + { + return 'DELETE' + . $this->buildFlags() + . $this->buildFrom() + . $this->buildWhere() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildReturning(); + } + + /** + * 生成 SELECT 列语句 + * + * @return string + * @throws Exception + */ + protected function buildCols() + { + if (!$this->cols) { + throw new Exception('No columns in the SELECT.'); + } + + $cols = array(); + foreach ($this->cols as $key => $val) { + if (is_int($key)) { + $cols[] = $this->quoteNamesIn($val); + } else { + $cols[] = $this->quoteNamesIn("$val AS $key"); + } + } + + return $this->indentCsv($cols); + } + + /** + * 生成 FROM 语句. + * + * @return string + */ + protected function buildFrom() + { + if (!$this->from) { + return ''; + } + + $refs = array(); + foreach ($this->from as $from) { + $refs[] = implode(' ', $from); + } + return ' FROM' . $this->indentCsv($refs); + } + + /** + * 生成 GROUP BY 语句. + * + * @return string + */ + protected function buildGroupBy() + { + if (!$this->group_by) { + return ''; + } + return ' GROUP BY' . $this->indentCsv($this->group_by); + } + + /** + * 生成 HAVING 语句. + * + * @return string + */ + protected function buildHaving() + { + if (!$this->having) { + return ''; + } + return ' HAVING' . $this->indent($this->having); + } + + /** + * 生成 FOR UPDATE 语句 + * + * @return string + */ + protected function buildForUpdate() + { + if (!$this->for_update) { + return ''; + } + return ' FOR UPDATE'; + } + + /** + * where + * + * @param string|array $cond + * @return self + */ + public function where($cond) + { + if (is_array($cond)) { + foreach ($cond as $key => $val) { + if (is_string($key)) { + $this->addWhere('AND', array($key, $val)); + } else { + $this->addWhere('AND', array($val)); + } + } + } else { + $this->addWhere('AND', func_get_args()); + } + return $this; + } + + /** + * or where + * + * @param string|array $cond + * @return self + */ + public function orWhere($cond) + { + if (is_array($cond)) { + foreach ($cond as $key => $val) { + if (is_string($key)) { + $this->addWhere('OR', array($key, $val)); + } else { + $this->addWhere('OR', array($val)); + } + } + } else { + $this->addWhere('OR', func_get_args()); + } + return $this; + } + + /** + * limit + * + * @param int $limit + * @return self + */ + public function limit($limit) + { + $this->limit = (int)$limit; + return $this; + } + + /** + * limit offset + * + * @param int $offset + * @return self + */ + public function offset($offset) + { + $this->offset = (int)$offset; + return $this; + } + + /** + * orderby. + * + * @param array $cols + * @return self + */ + public function orderBy(array $cols) + { + return $this->addOrderBy($cols); + } + + /** + * order by ASC OR DESC + * + * @param array $cols + * @param bool $order_asc + * @return self + */ + public function orderByASC(array $cols, $order_asc = true) + { + $this->order_asc = $order_asc; + return $this->addOrderBy($cols); + } + + /** + * order by DESC + * + * @param array $cols + * @return self + */ + public function orderByDESC(array $cols) + { + $this->order_asc = false; + return $this->addOrderBy($cols); + } + + // -------------abstractquery---------- + /** + * 返回逗号分隔的字符串 + * + * @param array $list + * @return string + */ + protected function indentCsv(array $list) + { + return ' ' . implode(',', $list); + } + + /** + * 返回空格分隔的字符串 + * + * @param array $list + * @return string + */ + protected function indent(array $list) + { + return ' ' . implode(' ', $list); + } + + /** + * 批量为占位符绑定值 + * + * @param array $bind_values + * @return self + * + */ + public function bindValues(array $bind_values) + { + foreach ($bind_values as $key => $val) { + $this->bindValue($key, $val); + } + return $this; + } + + /** + * 单个为占位符绑定值 + * + * @param string $name + * @param mixed $value + * @return self + */ + public function bindValue($name, $value) + { + $this->bind_values[$name] = $value; + return $this; + } + + /** + * 生成 flag + * + * @return string + */ + protected function buildFlags() + { + if (!$this->flags) { + return ''; + } + return ' ' . implode(' ', array_keys($this->flags)); + } + + /** + * 设置 flag. + * + * @param string $flag + * @param bool $enable + */ + protected function setFlag($flag, $enable = true) + { + if ($enable) { + $this->flags[$flag] = true; + } else { + unset($this->flags[$flag]); + } + } + + /** + * 重置 flag + */ + protected function resetFlags() + { + $this->flags = array(); + } + + /** + * + * 添加 where 语句 + * + * @param string $andor 'AND' or 'OR + * @param array $conditions + * @return self + * + */ + protected function addWhere($andor, $conditions) + { + $this->addClauseCondWithBind('where', $andor, $conditions); + return $this; + } + + /** + * 添加条件和绑定值 + * + * @param string $clause where 、having等 + * @param string $andor AND、OR等 + * @param array $conditions + */ + protected function addClauseCondWithBind($clause, $andor, $conditions) + { + $cond = array_shift($conditions); + $cond = $this->quoteNamesIn($cond); + + $bind =& $this->{"bind_{$clause}"}; + foreach ($conditions as $value) { + $bind[] = $value; + } + + $clause =& $this->$clause; + if ($clause) { + $clause[] = "$andor $cond"; + } else { + $clause[] = $cond; + } + } + + /** + * 生成 where 语句 + * + * @return string + */ + protected function buildWhere() + { + if (!$this->where) { + return ''; + } + return ' WHERE' . $this->indent($this->where); + } + + /** + * 增加 order by + * + * @param array $spec The columns and direction to order by. + * @return self + */ + protected function addOrderBy(array $spec) + { + foreach ($spec as $col) { + $this->order_by[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * 生成 order by 语句 + * + * @return string + */ + protected function buildOrderBy() + { + if (!$this->order_by) { + return ''; + } + + if ($this->order_asc) { + return ' ORDER BY' . $this->indentCsv($this->order_by) . ' ASC'; + } else { + return ' ORDER BY' . $this->indentCsv($this->order_by) . ' DESC'; + } + } + + /** + * 生成 limit 语句 + * + * @return string + */ + protected function buildLimit() + { + $has_limit = $this->type == 'DELETE' || $this->type == 'UPDATE'; + $has_offset = $this->type == 'SELECT'; + + if ($has_offset && $this->limit) { + $clause = " LIMIT {$this->limit}"; + if ($this->offset) { + $clause .= " OFFSET {$this->offset}"; + } + return $clause; + } elseif ($has_limit && $this->limit) { + return " LIMIT {$this->limit}"; + } + return ''; + } + + /** + * Quotes + * + * @param string $spec + * @return string|array + */ + public function quoteName($spec) + { + $spec = trim($spec); + $seps = array(' AS ', ' ', '.'); + foreach ($seps as $sep) { + $pos = strripos($spec, $sep); + if ($pos) { + return $this->quoteNameWithSeparator($spec, $sep, $pos); + } + } + return $this->replaceName($spec); + } + + /** + * 指定分隔符的 Quotes + * + * @param string $spec + * @param string $sep + * @param int $pos + * @return string + */ + protected function quoteNameWithSeparator($spec, $sep, $pos) + { + $len = strlen($sep); + $part1 = $this->quoteName(substr($spec, 0, $pos)); + $part2 = $this->replaceName(substr($spec, $pos + $len)); + return "{$part1}{$sep}{$part2}"; + } + + /** + * Quotes "table.col" 格式的字符串 + * + * @param string $text + * @return string|array + */ + public function quoteNamesIn($text) + { + $list = $this->getListForQuoteNamesIn($text); + $last = count($list) - 1; + $text = null; + foreach ($list as $key => $val) { + if (($key + 1) % 3) { + $text .= $this->quoteNamesInLoop($val, $key == $last); + } + } + return $text; + } + + /** + * 返回 quote 元素列表 + * + * @param string $text + * @return array + */ + protected function getListForQuoteNamesIn($text) + { + $apos = "'"; + $quot = '"'; + return preg_split( + "/(($apos+|$quot+|\\$apos+|\\$quot+).*?\\2)/", + $text, + -1, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * 循环 quote + * + * @param string $val + * @param bool $is_last + * @return string + */ + protected function quoteNamesInLoop($val, $is_last) + { + if ($is_last) { + return $this->replaceNamesAndAliasIn($val); + } + return $this->replaceNamesIn($val); + } + + /** + * 替换成别名 + * + * @param string $val + * @return string + */ + protected function replaceNamesAndAliasIn($val) + { + $quoted = $this->replaceNamesIn($val); + $pos = strripos($quoted, ' AS '); + if ($pos) { + $alias = $this->replaceName(substr($quoted, $pos + 4)); + $quoted = substr($quoted, 0, $pos) . " AS $alias"; + } + return $quoted; + } + + /** + * Quotes name + * + * @param string $name + * @return string + */ + protected function replaceName($name) + { + $name = trim($name); + if ($name == '*') { + return $name; + } + return '`' . $name . '`'; + } + + /** + * Quotes + * + * @param string $text + * @return string|array + */ + protected function replaceNamesIn($text) + { + $is_string_literal = strpos($text, "'") !== false + || strpos($text, '"') !== false; + if ($is_string_literal) { + return $text; + } + + $word = '[a-z_][a-z0-9_]+'; + + $find = "/(\\b)($word)\\.($word)(\\b)/i"; + + $repl = '$1`$2`.`$3`$4'; + + $text = preg_replace($find, $repl, $text); + + return $text; + } + + // ---------- insert -------------- + /** + * 设置 `table.column` 与 last-insert-id 的映射 + * + * @param array $last_insert_id_names + */ + public function setLastInsertIdNames(array $last_insert_id_names) + { + $this->last_insert_id_names = $last_insert_id_names; + } + + /** + * insert into. + * + * @param string $table + * @return self + */ + public function into($table) + { + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 生成 INSERT 语句 + * + * @return string + */ + protected function buildINSERT() + { + return 'INSERT' + . $this->buildFlags() + . $this->buildInto() + . $this->buildValuesForInsert() + . $this->buildReturning(); + } + + /** + * 生成 INTO 语句 + * + * @return string + */ + protected function buildInto() + { + return " INTO " . $this->table; + } + + /** + * PDO::lastInsertId() + * + * @param string $col + * @return mixed + */ + public function getLastInsertIdName($col) + { + $key = str_replace('`', '', $this->table) . '.' . $col; + if (isset($this->last_insert_id_names[$key])) { + return $this->last_insert_id_names[$key]; + } + + return null; + } + + /** + * 设置一列,如果有第二各参数,则把第二个参数绑定在占位符上 + * + * @param string $col + * @return self + */ + public function col($col) + { + return call_user_func_array(array($this, 'addCol'), func_get_args()); + } + + /** + * 设置多列 + * + * @param array $cols + * @return self + */ + public function cols(array $cols) + { + if ($this->type == 'SELECT') { + foreach ($cols as $key => $val) { + $this->addColSELECT($key, $val); + } + return $this; + } + return $this->addCols($cols); + } + + /** + * 直接设置列的值 + * + * @param string $col + * @param string $value + * @return self + */ + public function set($col, $value) + { + return $this->setCol($col, $value); + } + + /** + * 为 INSERT 语句绑定值 + * + * @return string + */ + protected function buildValuesForInsert() + { + return ' (' . $this->indentCsv(array_keys($this->col_values)) . ') VALUES (' . + $this->indentCsv(array_values($this->col_values)) . ')'; + } + + // ------update------- + /** + * 更新哪个表 + * + * @param string $table + * @return self + */ + public function table($table) + { + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 生成完整 SQL 语句 + * + * @return string + * @throws Exception + */ + protected function build() + { + switch ($this->type) { + case 'DELETE': + return $this->buildDELETE(); + case 'INSERT': + return $this->buildINSERT(); + case 'UPDATE': + return $this->buildUPDATE(); + case 'SELECT': + return $this->buildSELECT(); + } + throw new Exception("type empty"); + } + + /** + * 生成更新的 SQL 语句 + */ + protected function buildUPDATE() + { + return 'UPDATE' + . $this->buildFlags() + . $this->buildTable() + . $this->buildValuesForUpdate() + . $this->buildWhere() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildReturning(); + } + + /** + * 哪个表 + * + * @return string + */ + protected function buildTable() + { + return " {$this->table}"; + } + + /** + * 为更新语句绑定值 + * + * @return string + */ + protected function buildValuesForUpdate() + { + $values = array(); + foreach ($this->col_values as $col => $value) { + $values[] = "{$col} = {$value}"; + } + return ' SET' . $this->indentCsv($values); + } + + // ----------Dml--------------- + /** + * 获取绑定的值 + * + * @return array + */ + public function getBindValuesCOMMON() + { + $bind_values = $this->bind_values; + $i = 1; + foreach ($this->bind_where as $val) { + $bind_values[$i] = $val; + $i++; + } + return $bind_values; + } + + /** + * 设置列 + * + * @param string $col + * @return self + */ + protected function addCol($col) + { + $key = $this->quoteName($col); + $this->col_values[$key] = ":$col"; + $args = func_get_args(); + if (count($args) > 1) { + $this->bindValue($col, $args[1]); + } + return $this; + } + + /** + * 设置多个列 + * + * @param array $cols + * @return self + */ + protected function addCols(array $cols) + { + foreach ($cols as $key => $val) { + if (is_int($key)) { + $this->addCol($val); + } else { + $this->addCol($key, $val); + } + } + return $this; + } + + /** + * 设置单列的值 + * + * @param string $col . + * @param string $value + * @return self + */ + protected function setCol($col, $value) + { + if ($value === null) { + $value = 'NULL'; + } + + $key = $this->quoteName($col); + $value = $this->quoteNamesIn($value); + $this->col_values[$key] = $value; + return $this; + } + + /** + * 增加返回的列 + * + * @param array $cols + * @return self + * + */ + protected function addReturning(array $cols) + { + foreach ($cols as $col) { + $this->returning[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * 生成 RETURNING 语句 + * + * @return string + */ + protected function buildReturning() + { + if (!$this->returning) { + return ''; + } + return ' RETURNING' . $this->indentCsv($this->returning); + } + + /** + * 构造函数 + * + * @param string $host + * @param int $port + * @param string $user + * @param string $password + * @param string $db_name + * @param string $charset + */ + public function __construct($host, $port, $user, $password, $db_name, $charset = 'utf8') + { + $this->settings = array( + 'host' => $host, + 'port' => $port, + 'user' => $user, + 'password' => $password, + 'dbname' => $db_name, + 'charset' => $charset, + ); + $this->connect(); + } + + /** + * 创建 PDO 实例 + */ + protected function connect() + { + $dsn = 'mysql:dbname=' . $this->settings["dbname"] . ';host=' . + $this->settings["host"] . ';port=' . $this->settings['port']; + $this->pdo = new PDO($dsn, $this->settings["user"], $this->settings["password"], + array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . (!empty($this->settings['charset']) ? + $this->settings['charset'] : 'utf8') + )); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + /** + * 关闭连接 + */ + public function closeConnection() + { + $this->pdo = null; + } + + /** + * 执行 + * + * @param string $query + * @param string $parameters + * @throws PDOException + */ + protected function execute($query, $parameters = "") + { + try { + $this->sQuery = @$this->pdo->prepare($query); + $this->bindMore($parameters); + if (!empty($this->parameters)) { + foreach ($this->parameters as $param) { + $parameters = explode("\x7F", $param); + $this->sQuery->bindParam($parameters[0], $parameters[1]); + } + } + $this->success = $this->sQuery->execute(); + } catch (PDOException $e) { + // 服务端断开时重连一次 + if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) { + $this->closeConnection(); + $this->connect(); + + try { + $this->sQuery = $this->pdo->prepare($query); + $this->bindMore($parameters); + if (!empty($this->parameters)) { + foreach ($this->parameters as $param) { + $parameters = explode("\x7F", $param); + $this->sQuery->bindParam($parameters[0], $parameters[1]); + } + } + $this->success = $this->sQuery->execute(); + } catch (PDOException $ex) { + $this->rollBackTrans(); + throw $ex; + } + } else { + $this->rollBackTrans(); + $msg = $e->getMessage(); + $err_msg = "SQL:".$this->lastSQL()." ".$msg; + $exception = new \PDOException($err_msg, (int)$e->getCode()); + throw $exception; + } + } + $this->parameters = array(); + } + + /** + * 绑定 + * + * @param string $para + * @param string $value + */ + public function bind($para, $value) + { + if (is_string($para)) { + $this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value; + } else { + $this->parameters[sizeof($this->parameters)] = $para . "\x7F" . $value; + } + } + + /** + * 绑定多个 + * + * @param array $parray + */ + public function bindMore($parray) + { + if (empty($this->parameters) && is_array($parray)) { + $columns = array_keys($parray); + foreach ($columns as $i => &$column) { + $this->bind($column, $parray[$column]); + } + } + } + + /** + * 执行 SQL + * + * @param string $query + * @param array $params + * @param int $fetchmode + * @return mixed + */ + public function query($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + + $rawStatement = explode(" ", $query); + + $statement = strtolower(trim($rawStatement[0])); + if ($statement === 'select' || $statement === 'show') { + return $this->sQuery->fetchAll($fetchmode); + } elseif ($statement === 'update' || $statement === 'delete') { + return $this->sQuery->rowCount(); + } elseif ($statement === 'insert') { + if ($this->sQuery->rowCount() > 0) { + return $this->lastInsertId(); + } + } else { + return null; + } + + return null; + } + + /** + * 返回一列 + * + * @param string $query + * @param array $params + * @return array + */ + public function column($query = '', $params = null) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + $columns = $this->sQuery->fetchAll(PDO::FETCH_NUM); + $column = null; + foreach ($columns as $cells) { + $column[] = $cells[0]; + } + return $column; + } + + /** + * 返回一行 + * + * @param string $query + * @param array $params + * @param int $fetchmode + * @return array + */ + public function row($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + return $this->sQuery->fetch($fetchmode); + } + + /** + * 返回单个值 + * + * @param string $query + * @param array $params + * @return string + */ + public function single($query = '', $params = null) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + return $this->sQuery->fetchColumn(); + } + + /** + * 返回 lastInsertId + * + * @return string + */ + public function lastInsertId() + { + return $this->pdo->lastInsertId(); + } + + /** + * 返回最后一条执行的 sql + * + * @return string + */ + public function lastSQL() + { + return $this->lastSql; + } + + /** + * 开始事务 + */ + public function beginTrans() + { + try { + $this->pdo->beginTransaction(); + } catch (PDOException $e) { + // 服务端断开时重连一次 + if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) { + $this->pdo->beginTransaction(); + } else { + throw $e; + } + } + } + + /** + * 提交事务 + */ + public function commitTrans() + { + $this->pdo->commit(); + } + + /** + * 事务回滚 + */ + public function rollBackTrans() + { + if ($this->pdo->inTransaction()) { + $this->pdo->rollBack(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Gateway.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Gateway.php new file mode 100644 index 0000000..5df415d --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Lib/Gateway.php @@ -0,0 +1,919 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Exception; +use GatewayWorker\Protocols\GatewayProtocol; +use Workerman\Connection\TcpConnection; + +/** + * 数据发送相关 + */ +class Gateway +{ + /** + * gateway 实例 + * + * @var object + */ + protected static $businessWorker = null; + + /** + * 注册中心地址 + * + * @var string + */ + public static $registerAddress = '127.0.0.1:1236'; + + /** + * 秘钥 + * @var string + */ + public static $secretKey = ''; + + /** + * 链接超时时间 + * @var int + */ + public static $connectTimeout = 3; + + /** + * 与Gateway是否是长链接 + * @var bool + */ + public static $persistentConnection = true; + + /** + * 向所有客户端连接(或者 client_id_array 指定的客户端连接)广播消息 + * + * @param string $message 向客户端发送的消息 + * @param array $client_id_array 客户端 id 数组 + * @param array $exclude_client_id 不给这些client_id发 + * @param bool $raw 是否发送原始数据(即不调用gateway的协议的encode方法) + * @return void + */ + public static function sendToAll($message, $client_id_array = null, $exclude_client_id = null, $raw = false) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_ALL; + $gateway_data['body'] = $message; + if ($raw) { + $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE; + } + + if ($exclude_client_id) { + if (!is_array($exclude_client_id)) { + $exclude_client_id = array($exclude_client_id); + } + if ($client_id_array) { + $exclude_client_id = array_flip($exclude_client_id); + } + } + + if ($client_id_array) { + if (!is_array($client_id_array)) { + echo new \Exception('bad $client_id_array:'.var_export($client_id_array, true)); + return; + } + $data_array = array(); + foreach ($client_id_array as $client_id) { + if (isset($exclude_client_id[$client_id])) { + continue; + } + $address = Context::clientIdToAddress($client_id); + if ($address) { + $key = long2ip($address['local_ip']) . ":{$address['local_port']}"; + $data_array[$key][$address['connection_id']] = $address['connection_id']; + } + } + foreach ($data_array as $addr => $connection_id_list) { + $the_gateway_data = $gateway_data; + $the_gateway_data['ext_data'] = json_encode(array('connections' => $connection_id_list)); + self::sendToGateway($addr, $the_gateway_data); + } + return; + } elseif (empty($client_id_array) && is_array($client_id_array)) { + return; + } + + if (!$exclude_client_id) { + return self::sendToAllGateway($gateway_data); + } + + $address_connection_array = self::clientIdArrayToAddressArray($exclude_client_id); + + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (self::$businessWorker) { + foreach (self::$businessWorker->gatewayConnections as $address => $gateway_connection) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('exclude'=> $address_connection_array[$address])) : ''; + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($gateway_data); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = self::getAllGatewayAddressesFromRegister(); + if (!$all_addresses) { + throw new Exception('Gateway::getAllGatewayAddressesFromRegister() with registerAddress:' . + self::$registerAddress . ' return ' . var_export($all_addresses, true)); + } + foreach ($all_addresses as $address) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('exclude'=> $address_connection_array[$address])) : ''; + self::sendToGateway($address, $gateway_data); + } + } + + } + + /** + * 向某个客户端连接发消息 + * + * @param int $client_id + * @param string $message + * @return bool + */ + public static function sendToClient($client_id, $message) + { + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SEND_TO_ONE, $message); + } + + /** + * 向当前客户端连接发送消息 + * + * @param string $message + * @return bool + */ + public static function sendToCurrentClient($message) + { + return self::sendCmdAndMessageToClient(null, GatewayProtocol::CMD_SEND_TO_ONE, $message); + } + + /** + * 判断某个uid是否在线 + * + * @param string $uid + * @return int 0|1 + */ + public static function isUidOnline($uid) + { + return (int)self::getClientIdByUid($uid); + } + + /** + * 判断某个客户端连接是否在线 + * + * @param int $client_id + * @return int 0|1 + */ + public static function isOnline($client_id) + { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return 0; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (isset(self::$businessWorker)) { + if (!isset(self::$businessWorker->gatewayConnections[$address])) { + return 0; + } + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_IS_ONLINE; + $gateway_data['connection_id'] = $address_data['connection_id']; + return (int)self::sendAndRecv($address, $gateway_data); + } + + /** + * 获取所有在线用户的session,client_id为 key + * + * @param string $group + * @return array + */ + public static function getAllClientInfo($group = null) + { + return self::getAllClientSessions($group); + } + + /** + * 获取所有在线用户的session,client_id为 key + * + * @param string $group + * @return array + */ + public static function getAllClientSessions($group = null) + { + $gateway_data = GatewayProtocol::$empty; + if (!$group) { + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_ALL_CLIENT_INFO; + } else { + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLINET_INFO_BY_GROUP; + $gateway_data['ext_data'] = $group; + } + $status_data = array(); + $all_buffer_array = self::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $data) { + if ($data) { + foreach ($data as $connection_id => $session_buffer) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + if ($client_id === Context::$client_id) { + $status_data[$client_id] = (array)$_SESSION; + } else { + $status_data[$client_id] = $session_buffer ? Context::sessionDecode($session_buffer) : array(); + } + } + } + } + } + return $status_data; + } + + /** + * 获取某个组的连接信息 + * + * @param string $group + * @return array + */ + public static function getClientInfoByGroup($group) + { + return self::getAllClientSessions($group); + } + + /** + * 获取某个组的连接信息 + * + * @param string $group + * @return array + */ + public static function getClientSessionsByGroup($group) + { + return self::getAllClientSessions($group); + } + + /** + * 获取所有连接数 + * + * @return int + */ + public static function getAllClientCount() + { + return self::getClientCountByGroup(); + } + + /** + * 获取某个组的在线连接数 + * + * @param string $group + * @return int + */ + public static function getClientCountByGroup($group = '') + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP; + $gateway_data['ext_data'] = $group; + $total_count = 0; + $all_buffer_array = self::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $count) { + if ($count) { + $total_count += $count; + } + } + } + return $total_count; + } + + /** + * 获取与 uid 绑定的 client_id 列表 + * + * @param string $uid + * @return array + */ + public static function getClientIdByUid($uid) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID; + $gateway_data['ext_data'] = $uid; + $client_list = array(); + $all_buffer_array = self::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $connection_id_array) { + if ($connection_id_array) { + foreach ($connection_id_array as $connection_id) { + $client_list[] = Context::addressToClientId($local_ip, $local_port, $connection_id); + } + } + } + } + return $client_list; + } + + /** + * 生成验证包,用于验证此客户端的合法性 + * + * @return string + */ + protected static function generateAuthBuffer() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT; + $gateway_data['body'] = json_encode(array( + 'secret_key' => self::$secretKey, + )); + return GatewayProtocol::encode($gateway_data); + } + + /** + * 批量向所有 gateway 发包,并得到返回数组 + * + * @param string $gateway_data + * @return array + * @throws Exception + */ + protected static function getBufferFromAllGateway($gateway_data) + { + $gateway_buffer = GatewayProtocol::encode($gateway_data); + $gateway_buffer = self::$secretKey ? self::generateAuthBuffer() . $gateway_buffer : $gateway_buffer; + if (isset(self::$businessWorker)) { + $all_addresses = self::$businessWorker->getAllGatewayAddresses(); + if (empty($all_addresses)) { + throw new Exception('businessWorker::getAllGatewayAddresses return empty'); + } + } else { + $all_addresses = self::getAllGatewayAddressesFromRegister(); + if (empty($all_addresses)) { + return array(); + } + } + $client_array = $status_data = $client_address_map = $receive_buffer_array = $recv_length_array = array(); + // 批量向所有gateway进程发送请求数据 + foreach ($all_addresses as $address) { + $client = stream_socket_client("tcp://$address", $errno, $errmsg, self::$connectTimeout); + if ($client && strlen($gateway_buffer) === stream_socket_sendto($client, $gateway_buffer)) { + $socket_id = (int)$client; + $client_array[$socket_id] = $client; + $client_address_map[$socket_id] = explode(':', $address); + $receive_buffer_array[$socket_id] = ''; + } + } + // 超时5秒 + $timeout = 5; + $time_start = microtime(true); + // 批量接收请求 + while (count($client_array) > 0) { + $write = $except = array(); + $read = $client_array; + if (@stream_select($read, $write, $except, $timeout)) { + foreach ($read as $client) { + $socket_id = (int)$client; + $buffer = stream_socket_recvfrom($client, 65535); + if ($buffer !== '' && $buffer !== false) { + $receive_buffer_array[$socket_id] .= $buffer; + $receive_length = strlen($receive_buffer_array[$socket_id]); + if (empty($recv_length_array[$socket_id]) && $receive_length >= 4) { + $recv_length_array[$socket_id] = current(unpack('N', $receive_buffer_array[$socket_id])); + } + if (!empty($recv_length_array[$socket_id]) && $receive_length >= $recv_length_array[$socket_id] + 4) { + unset($client_array[$socket_id]); + } + } elseif (feof($client)) { + unset($client_array[$socket_id]); + } + } + } + if (microtime(true) - $time_start > $timeout) { + break; + } + } + $format_buffer_array = array(); + foreach ($receive_buffer_array as $socket_id => $buffer) { + $local_ip = ip2long($client_address_map[$socket_id][0]); + $local_port = $client_address_map[$socket_id][1]; + $format_buffer_array[$local_ip][$local_port] = unserialize(substr($buffer, 4)); + } + return $format_buffer_array; + } + + /** + * 踢掉某个客户端,并以$message通知被踢掉客户端 + * + * @param int $client_id + * @param string $message + * @return bool + */ + public static function closeClient($client_id, $message = null) + { + if ($client_id === Context::$client_id) { + return self::closeCurrentClient($message); + } // 不是发给当前用户则使用存储中的地址 + else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + return self::kickAddress($address, $address_data['connection_id'], $message); + } + } + + /** + * 踢掉当前客户端,并以$message通知被踢掉客户端 + * + * @param string $message + * @return bool + * @throws Exception + */ + public static function closeCurrentClient($message = null) + { + if (!Context::$connection_id) { + throw new Exception('closeCurrentClient can not be called in async context'); + } + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + return self::kickAddress($address, Context::$connection_id, $message); + } + + /** + * 踢掉某个客户端并直接立即销毁相关连接 + * + * @param int $client_id + * @return bool + */ + public static function destoryClient($client_id) + { + if ($client_id === Context::$client_id) { + return self::destoryCurrentClient(); + } // 不是发给当前用户则使用存储中的地址 + else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + return self::destroyAddress($address, $address_data['connection_id']); + } + } + + /** + * 踢掉当前客户端并直接立即销毁相关连接 + * + * @return bool + * @throws Exception + */ + public static function destoryCurrentClient() + { + if (!Context::$connection_id) { + throw new Exception('destoryCurrentClient can not be called in async context'); + } + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + return self::destroyAddress($address, Context::$connection_id); + } + + /** + * 将 client_id 与 uid 绑定 + * + * @param int $client_id + * @param int|string $uid + * @return bool + */ + public static function bindUid($client_id, $uid) + { + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_BIND_UID, '', $uid); + } + + /** + * 将 client_id 与 uid 解除绑定 + * + * @param int $client_id + * @param int|string $uid + * @return bool + */ + public static function unbindUid($client_id, $uid) + { + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UNBIND_UID, '', $uid); + } + + /** + * 将 client_id 加入组 + * + * @param int $client_id + * @param int|string $group + * @return bool + */ + public static function joinGroup($client_id, $group) + { + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_JOIN_GROUP, '', $group); + } + + /** + * 将 client_id 离开组 + * + * @param int $client_id + * @param int|string $group + * @return bool + */ + public static function leaveGroup($client_id, $group) + { + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_LEAVE_GROUP, '', $group); + } + + /** + * 向所有 uid 发送 + * + * @param int|string|array $uid + * @param string $message + */ + public static function sendToUid($uid, $message) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_UID; + $gateway_data['body'] = $message; + + if (!is_array($uid)) { + $uid = array($uid); + } + + $gateway_data['ext_data'] = json_encode($uid); + + self::sendToAllGateway($gateway_data); + } + + /** + * 向 group 发送 + * + * @param int|string|array $group 组(不允许是 0 '0' false null array()等为空的值) + * @param string $message 消息 + * @param array $exclude_client_id 不给这些client_id发 + * @param bool $raw 发送原始数据(即不调用gateway的协议的encode方法) + */ + public static function sendToGroup($group, $message, $exclude_client_id = null, $raw = false) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_GROUP; + $gateway_data['body'] = $message; + if ($raw) { + $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE; + } + + if (!is_array($group)) { + $group = array($group); + } + + // 分组发送,没有排除的client_id,直接发送 + $default_ext_data_buffer = json_encode(array('group'=> $group, 'exclude'=> null)); + if (empty($exclude_client_id)) { + $gateway_data['ext_data'] = $default_ext_data_buffer; + return self::sendToAllGateway($gateway_data); + } + + // 分组发送,有排除的client_id,需要将client_id转换成对应gateway进程内的connectionId + if (!is_array($exclude_client_id)) { + $exclude_client_id = array($exclude_client_id); + } + + $address_connection_array = self::clientIdArrayToAddressArray($exclude_client_id); + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (self::$businessWorker) { + foreach (self::$businessWorker->gatewayConnections as $address => $gateway_connection) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) : + $default_ext_data_buffer; + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($gateway_data); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = self::getAllGatewayAddressesFromRegister(); + if (!$all_addresses) { + throw new Exception('Gateway::getAllGatewayAddressesFromRegister() with registerAddress:' . + self::$registerAddress . ' return ' . var_export($all_addresses, true)); + } + foreach ($all_addresses as $address) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) : + $default_ext_data_buffer; + self::sendToGateway($address, $gateway_data); + } + } + } + + /** + * 更新 session,框架自动调用,开发者不要调用 + * + * @param int $client_id + * @param string $session_str + * @return bool + */ + public static function setSocketSession($client_id, $session_str) + { + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SET_SESSION, '', $session_str); + } + + /** + * 设置 session,原session值会被覆盖 + * + * @param int $client_id + * @param array $session + */ + public static function setSession($client_id, array $session) + { + if (Context::$client_id === $client_id) { + $_SESSION = $session; + Context::$old_session = $_SESSION; + } + return self::setSocketSession($client_id, Context::sessionEncode($session)); + } + + /** + * 更新 session,实际上是与老的session合并 + * + * @param int $client_id + * @param array $session + */ + public static function updateSession($client_id, array $session) + { + if (Context::$client_id === $client_id) { + $_SESSION = $session + (array)$_SESSION; + Context::$old_session = $_SESSION; + } + return self::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UPDATE_SESSION, '', Context::sessionEncode($session)); + } + + /** + * 获取某个client_id的session + * + * @param int $client_id + * @return mixed false表示出错、null表示用户不存在、array表示具体的session信息 + */ + public static function getSession($client_id) + { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (isset(self::$businessWorker)) { + if (!isset(self::$businessWorker->gatewayConnections[$address])) { + return null; + } + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID; + $gateway_data['connection_id'] = $address_data['connection_id']; + return self::sendAndRecv($address, $gateway_data); + } + + /** + * 向某个用户网关发送命令和消息 + * + * @param int $client_id + * @param int $cmd + * @param string $message + * @param string $ext_data + * @return boolean + */ + protected static function sendCmdAndMessageToClient($client_id, $cmd, $message, $ext_data = '') + { + // 如果是发给当前用户则直接获取上下文中的地址 + if ($client_id === Context::$client_id || $client_id === null) { + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + $connection_id = Context::$connection_id; + } else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + $connection_id = $address_data['connection_id']; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = $cmd; + $gateway_data['connection_id'] = $connection_id; + $gateway_data['body'] = $message; + if (!empty($ext_data)) { + $gateway_data['ext_data'] = $ext_data; + } + + return self::sendToGateway($address, $gateway_data); + } + + /** + * 发送数据并返回 + * + * @param int $address + * @param mixed $data + * @return bool + * @throws Exception + */ + protected static function sendAndRecv($address, $data) + { + $buffer = GatewayProtocol::encode($data); + $buffer = self::$secretKey ? self::generateAuthBuffer() . $buffer : $buffer; + $client = stream_socket_client("tcp://$address", $errno, $errmsg, self::$connectTimeout); + if (!$client) { + throw new Exception("can not connect to tcp://$address $errmsg"); + } + if (strlen($buffer) === stream_socket_sendto($client, $buffer)) { + $timeout = 5; + // 阻塞读 + stream_set_blocking($client, 1); + // 1秒超时 + stream_set_timeout($client, 1); + $all_buffer = ''; + $time_start = microtime(true); + $pack_len = 0; + while (1) { + $buf = stream_socket_recvfrom($client, 655350); + if ($buf !== '' && $buf !== false) { + $all_buffer .= $buf; + } else { + if (feof($client)) { + throw new Exception("connection close tcp://$address"); + } elseif (microtime(true) - $time_start > $timeout) { + break; + } + continue; + } + $recv_len = strlen($all_buffer); + if (!$pack_len && $recv_len >= 4) { + $pack_len= current(unpack('N', $all_buffer)); + } + // 回复的数据都是以\n结尾 + if (($pack_len && $recv_len >= $pack_len + 4) || microtime(true) - $time_start > $timeout) { + break; + } + } + // 返回结果 + return unserialize(substr($all_buffer, 4)); + } else { + throw new Exception("sendAndRecv($address, \$bufer) fail ! Can not send data!", 502); + } + } + + /** + * 发送数据到网关 + * + * @param string $address + * @param array $gateway_data + * @return bool + */ + protected static function sendToGateway($address, $gateway_data) + { + return self::sendBufferToGateway($address, GatewayProtocol::encode($gateway_data)); + } + + /** + * 发送buffer数据到网关 + * @param string $address + * @param string $gateway_buffer + * @return bool + */ + protected static function sendBufferToGateway($address, $gateway_buffer) + { + // 有$businessWorker说明是workerman环境,使用$businessWorker发送数据 + if (self::$businessWorker) { + if (!isset(self::$businessWorker->gatewayConnections[$address])) { + return false; + } + return self::$businessWorker->gatewayConnections[$address]->send($gateway_buffer, true); + } + // 非workerman环境 + $gateway_buffer = self::$secretKey ? self::generateAuthBuffer() . $gateway_buffer : $gateway_buffer; + $flag = self::$persistentConnection ? STREAM_CLIENT_PERSISTENT | STREAM_CLIENT_CONNECT : STREAM_CLIENT_CONNECT; + $client = stream_socket_client("tcp://$address", $errno, $errmsg, self::$connectTimeout, $flag); + return strlen($gateway_buffer) == stream_socket_sendto($client, $gateway_buffer); + } + + /** + * 向所有 gateway 发送数据 + * + * @param string $gateway_data + * @throws Exception + */ + protected static function sendToAllGateway($gateway_data) + { + $buffer = GatewayProtocol::encode($gateway_data); + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (self::$businessWorker) { + foreach (self::$businessWorker->gatewayConnections as $gateway_connection) { + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($buffer, true); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = self::getAllGatewayAddressesFromRegister(); + if (!$all_addresses) { + throw new Exception('Gateway::getAllGatewayAddressesFromRegister() with registerAddress:' . + self::$registerAddress . ' return ' . var_export($all_addresses, true)); + } + foreach ($all_addresses as $address) { + self::sendBufferToGateway($address, $buffer); + } + } + } + + /** + * 踢掉某个网关的 socket + * + * @param string $address + * @param int $connection_id + * @return bool + */ + protected static function kickAddress($address, $connection_id, $message) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_KICK; + $gateway_data['connection_id'] = $connection_id; + $gateway_data['body'] = $message; + return self::sendToGateway($address, $gateway_data); + } + + /** + * 销毁某个网关的 socket + * + * @param string $address + * @param int $connection_id + * @return bool + */ + protected static function destroyAddress($address, $connection_id) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_DESTROY; + $gateway_data['connection_id'] = $connection_id; + return self::sendToGateway($address, $gateway_data); + } + + /** + * 将clientid数组转换成address数组 + * + * @param array $client_id_array + * @return array + */ + protected static function clientIdArrayToAddressArray(array $client_id_array) + { + $address_connection_array = array(); + foreach ($client_id_array as $client_id) { + $address_data = Context::clientIdToAddress($client_id); + if ($address_data) { + $address = long2ip($address_data['local_ip']) . + ":{$address_data['local_port']}"; + $address_connection_array[$address][$address_data['connection_id']] = $address_data['connection_id']; + } + } + return $address_connection_array; + } + + /** + * 设置 gateway 实例 + * + * @param \GatewayWorker\BusinessWorker $business_worker_instance + */ + public static function setBusinessWorker($business_worker_instance) + { + self::$businessWorker = $business_worker_instance; + } + + /** + * 获取通过注册中心获取所有 gateway 通讯地址 + * + * @return array + * @throws Exception + */ + protected static function getAllGatewayAddressesFromRegister() + { + static $addresses_cache, $last_update; + $time_now = time(); + $expiration_time = 1; + if(empty($addresses_cache) || $time_now - $last_update > $expiration_time) { + $client = stream_socket_client('tcp://' . self::$registerAddress, $errno, $errmsg, self::$connectTimeout); + if (!$client) { + throw new Exception('Can not connect to tcp://' . self::$registerAddress . ' ' . $errmsg); + } + fwrite($client, '{"event":"worker_connect","secret_key":"' . self::$secretKey . '"}' . "\n"); + stream_set_timeout($client, 5); + $ret = fgets($client, 655350); + if (!$ret || !$data = json_decode(trim($ret), true)) { + throw new Exception('getAllGatewayAddressesFromRegister fail. tcp://' . + self::$registerAddress . ' return ' . var_export($ret, true)); + } + $last_update = $time_now; + $addresses_cache = $data['addresses']; + } + return $addresses_cache; + } +} + +if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Protocols/GatewayProtocol.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Protocols/GatewayProtocol.php new file mode 100644 index 0000000..3abed67 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Protocols/GatewayProtocol.php @@ -0,0 +1,204 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Protocols; + +/** + * Gateway 与 Worker 间通讯的二进制协议 + * + * struct GatewayProtocol + * { + * unsigned int pack_len, + * unsigned char cmd,//命令字 + * unsigned int local_ip, + * unsigned short local_port, + * unsigned int client_ip, + * unsigned short client_port, + * unsigned int connection_id, + * unsigned char flag, + * unsigned short gateway_port, + * unsigned int ext_len, + * char[ext_len] ext_data, + * char[pack_length-HEAD_LEN] body//包体 + * } + * NCNnNnNCnN + */ +class GatewayProtocol +{ + // 发给worker,gateway有一个新的连接 + const CMD_ON_CONNECTION = 1; + + // 发给worker的,客户端有消息 + const CMD_ON_MESSAGE = 3; + + // 发给worker上的关闭链接事件 + const CMD_ON_CLOSE = 4; + + // 发给gateway的向单个用户发送数据 + const CMD_SEND_TO_ONE = 5; + + // 发给gateway的向所有用户发送数据 + const CMD_SEND_TO_ALL = 6; + + // 发给gateway的踢出用户 + // 1、如果有待发消息,将在发送完后立即销毁用户连接 + // 2、如果无待发消息,将立即销毁用户连接 + const CMD_KICK = 7; + + // 发给gateway的立即销毁用户连接 + const CMD_DESTROY = 8; + + // 发给gateway,通知用户session更新 + const CMD_UPDATE_SESSION = 9; + + // 获取在线状态 + const CMD_GET_ALL_CLIENT_INFO = 10; + + // 判断是否在线 + const CMD_IS_ONLINE = 11; + + // client_id绑定到uid + const CMD_BIND_UID = 12; + + // 解绑 + const CMD_UNBIND_UID = 13; + + // 向uid发送数据 + const CMD_SEND_TO_UID = 14; + + // 根据uid获取绑定的clientid + const CMD_GET_CLIENT_ID_BY_UID = 15; + + // 加入组 + const CMD_JOIN_GROUP = 20; + + // 离开组 + const CMD_LEAVE_GROUP = 21; + + // 向组成员发消息 + const CMD_SEND_TO_GROUP = 22; + + // 获取组成员 + const CMD_GET_CLINET_INFO_BY_GROUP = 23; + + // 获取组成员数 + const CMD_GET_CLIENT_COUNT_BY_GROUP = 24; + + // worker连接gateway事件 + const CMD_WORKER_CONNECT = 200; + + // 心跳 + const CMD_PING = 201; + + // GatewayClient连接gateway事件 + const CMD_GATEWAY_CLIENT_CONNECT = 202; + + // 根据client_id获取session + const CMD_GET_SESSION_BY_CLIENT_ID = 203; + + // 发给gateway,覆盖session + const CMD_SET_SESSION = 204; + + // 包体是标量 + const FLAG_BODY_IS_SCALAR = 0x01; + + // 通知gateway在send时不调用协议encode方法,在广播组播时提升性能 + const FLAG_NOT_CALL_ENCODE = 0x02; + + /** + * 包头长度 + * + * @var int + */ + const HEAD_LEN = 28; + + public static $empty = array( + 'cmd' => 0, + 'local_ip' => 0, + 'local_port' => 0, + 'client_ip' => 0, + 'client_port' => 0, + 'connection_id' => 0, + 'flag' => 0, + 'gateway_port' => 0, + 'ext_data' => '', + 'body' => '', + ); + + /** + * 返回包长度 + * + * @param string $buffer + * @return int return current package length + */ + public static function input($buffer) + { + if (strlen($buffer) < self::HEAD_LEN) { + return 0; + } + + $data = unpack("Npack_len", $buffer); + return $data['pack_len']; + } + + /** + * 获取整个包的 buffer + * + * @param mixed $data + * @return string + */ + public static function encode($data) + { + $flag = (int)is_scalar($data['body']); + if (!$flag) { + $data['body'] = serialize($data['body']); + } + $data['flag'] |= $flag; + $ext_len = strlen($data['ext_data']); + $package_len = self::HEAD_LEN + $ext_len + strlen($data['body']); + return pack("NCNnNnNCnN", $package_len, + $data['cmd'], $data['local_ip'], + $data['local_port'], $data['client_ip'], + $data['client_port'], $data['connection_id'], + $data['flag'], $data['gateway_port'], + $ext_len) . $data['ext_data'] . $data['body']; + } + + /** + * 从二进制数据转换为数组 + * + * @param string $buffer + * @return array + */ + public static function decode($buffer) + { + $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nconnection_id/Cflag/ngateway_port/Next_len", + $buffer); + if ($data['ext_len'] > 0) { + $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']); + if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) { + $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']); + } else { + $data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len'])); + } + } else { + $data['ext_data'] = ''; + if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) { + $data['body'] = substr($buffer, self::HEAD_LEN); + } else { + $data['body'] = unserialize(substr($buffer, self::HEAD_LEN)); + } + } + return $data; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Register.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Register.php new file mode 100644 index 0000000..2d3e1a4 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker-for-win/src/Register.php @@ -0,0 +1,190 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use Workerman\Worker; +use Workerman\Lib\Timer; + +/** + * + * 注册中心,用于注册 Gateway 和 BusinessWorker + * + * @author walkor + * + */ +class Register extends Worker +{ + /** + * {@inheritdoc} + */ + public $name = 'Register'; + + /** + * {@inheritdoc} + */ + public $reloadable = false; + + /** + * 秘钥 + * @var string + */ + public $secretKey = ''; + + /** + * 所有 gateway 的连接 + * + * @var array + */ + protected $_gatewayConnections = array(); + + /** + * 所有 worker 的连接 + * + * @var array + */ + protected $_workerConnections = array(); + + /** + * 进程启动时间 + * + * @var int + */ + protected $_startTime = 0; + + /** + * {@inheritdoc} + */ + public function run() + { + // 设置 onMessage 连接回调 + $this->onConnect = array($this, 'onConnect'); + + // 设置 onMessage 回调 + $this->onMessage = array($this, 'onMessage'); + + // 设置 onClose 回调 + $this->onClose = array($this, 'onClose'); + + // 记录进程启动的时间 + $this->_startTime = time(); + + // 强制使用text协议 + $this->protocol = '\Workerman\Protocols\Text'; + + // 运行父方法 + parent::run(); + } + + /** + * 设置个定时器,将未及时发送验证的连接关闭 + * + * @param \Workerman\Connection\ConnectionInterface $connection + * @return void + */ + public function onConnect($connection) + { + $connection->timeout_timerid = Timer::add(10, function () use ($connection) { + Worker::log("Register auth timeout (".$connection->getRemoteIp()."). See http://wiki.workerman.net/Error4 for detail"); + $connection->close(); + }, null, false); + } + + /** + * 设置消息回调 + * + * @param \Workerman\Connection\ConnectionInterface $connection + * @param string $buffer + * @return void + */ + public function onMessage($connection, $buffer) + { + // 删除定时器 + Timer::del($connection->timeout_timerid); + $data = @json_decode($buffer, true); + if (empty($data['event'])) { + $error = "Bad request for Register service. Request info(IP:".$connection->getRemoteIp().", Request Buffer:$buffer). See http://wiki.workerman.net/Error4 for detail"; + Worker::log($error); + return $connection->close($error); + } + $event = $data['event']; + $secret_key = isset($data['secret_key']) ? $data['secret_key'] : ''; + // 开始验证 + switch ($event) { + // 是 gateway 连接 + case 'gateway_connect': + if (empty($data['address'])) { + echo "address not found\n"; + return $connection->close(); + } + if ($secret_key !== $this->secretKey) { + Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $this->_gatewayConnections[$connection->id] = $data['address']; + $this->broadcastAddresses(); + break; + // 是 worker 连接 + case 'worker_connect': + if ($secret_key !== $this->secretKey) { + Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $this->_workerConnections[$connection->id] = $connection; + $this->broadcastAddresses($connection); + break; + case 'ping': + break; + default: + Worker::log("Register unknown event:$event IP: ".$connection->getRemoteIp()." Buffer:$buffer. See http://wiki.workerman.net/Error4 for detail"); + $connection->close(); + } + } + + /** + * 连接关闭时 + * + * @param \Workerman\Connection\ConnectionInterface $connection + */ + public function onClose($connection) + { + if (isset($this->_gatewayConnections[$connection->id])) { + unset($this->_gatewayConnections[$connection->id]); + $this->broadcastAddresses(); + } + if (isset($this->_workerConnections[$connection->id])) { + unset($this->_workerConnections[$connection->id]); + } + } + + /** + * 向 BusinessWorker 广播 gateway 内部通讯地址 + * + * @param \Workerman\Connection\ConnectionInterface $connection + */ + public function broadcastAddresses($connection = null) + { + $data = array( + 'event' => 'broadcast_addresses', + 'addresses' => array_unique(array_values($this->_gatewayConnections)), + ); + $buffer = json_encode($data); + if ($connection) { + $connection->send($buffer); + return; + } + foreach ($this->_workerConnections as $con) { + $con->send($buffer); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/.gitignore b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/.gitignore new file mode 100644 index 0000000..8cb4441 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/.gitignore @@ -0,0 +1,4 @@ +.buildpath +.project +.settings +.idea \ No newline at end of file diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/README.md b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/README.md new file mode 100644 index 0000000..a1365a5 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/README.md @@ -0,0 +1,38 @@ +GatewayWorker +================= + +GatewayWorker基于[Workerman](https://github.com/walkor/Workerman)开发的一个项目框架,用于快速开发长连接应用,例如app推送服务端、即时IM服务端、游戏服务端、物联网、智能家居等等。 + +GatewayWorker使用经典的Gateway和Worker进程模型。Gateway进程负责维持客户端连接,并转发客户端的数据给Worker进程处理;Worker进程负责处理实际的业务逻辑,并将结果推送给对应的客户端。Gateway服务和Worker服务可以分开部署在不同的服务器上,实现分布式集群。 + +GatewayWorker提供非常方便的API,可以全局广播数据、可以向某个群体广播数据、也可以向某个特定客户端推送数据。配合Workerman的定时器,也可以定时推送数据。 + +快速开始 +====== +开发者可以从一个简单的demo开始(demo中包含了GatewayWorker内核,以及start_gateway.php start_business.php等启动入口文件)
+[点击这里下载demo](http://www.workerman.net/download/GatewayWorker.zip)。
+demo说明见源码readme。 + +手册 +======= +http://www.workerman.net/gatewaydoc/ + +安装内核 +======= + +只安装GatewayWorker内核文件(不包含start_gateway.php start_businessworker.php等启动入口文件) +``` +composer require workerman/gateway-worker +``` + +使用GatewayWorker开发的项目 +======= +## [tadpole](http://kedou.workerman.net/) +[Live demo](http://kedou.workerman.net/) +[Source code](https://github.com/walkor/workerman) +![workerman todpole](http://www.workerman.net/img/workerman-todpole.png) + +## [chat room](http://chat.workerman.net/) +[Live demo](http://chat.workerman.net/) +[Source code](https://github.com/walkor/workerman-chat) +![workerman-chat](http://www.workerman.net/img/workerman-chat.png) diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/composer.json b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/composer.json new file mode 100644 index 0000000..2f81674 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/composer.json @@ -0,0 +1,12 @@ +{ + "name" : "workerman/gateway-worker", + "keywords": ["distributed","communication"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/workerman" : ">=3.1.8" + }, + "autoload": { + "psr-4": {"GatewayWorker\\": "./src"} + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php new file mode 100644 index 0000000..d97d202 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/BusinessWorker.php @@ -0,0 +1,560 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use Workerman\Connection\TcpConnection; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\AsyncTcpConnection; +use GatewayWorker\Protocols\GatewayProtocol; +use GatewayWorker\Lib\Context; + +/** + * + * BusinessWorker 用于处理Gateway转发来的数据 + * + * @author walkor + * + */ +class BusinessWorker extends Worker +{ + /** + * 保存与 gateway 的连接 connection 对象 + * + * @var array + */ + public $gatewayConnections = array(); + + /** + * 注册中心地址 + * + * @var string|array + */ + public $registerAddress = '127.0.0.1:1236'; + + /** + * 事件处理类,默认是 Event 类 + * + * @var string + */ + public $eventHandler = 'Events'; + + /** + * 业务超时时间,可用来定位程序卡在哪里 + * + * @var int + */ + public $processTimeout = 30; + + /** + * 业务超时时间,可用来定位程序卡在哪里 + * + * @var callable + */ + public $processTimeoutHandler = '\\Workerman\\Worker::log'; + + /** + * 秘钥 + * + * @var string + */ + public $secretKey = ''; + + /** + * businessWorker进程将消息转发给gateway进程的发送缓冲区大小 + * + * @var int + */ + public $sendToGatewayBufferSize = 10240000; + + /** + * 保存用户设置的 worker 启动回调 + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * 保存用户设置的 workerReload 回调 + * + * @var callback + */ + protected $_onWorkerReload = null; + + /** + * 保存用户设置的 workerStop 回调 + * + * @var callback + */ + protected $_onWorkerStop= null; + + /** + * 到注册中心的连接 + * + * @var AsyncTcpConnection + */ + protected $_registerConnection = null; + + /** + * 处于连接状态的 gateway 通讯地址 + * + * @var array + */ + protected $_connectingGatewayAddresses = array(); + + /** + * 所有 geteway 内部通讯地址 + * + * @var array + */ + protected $_gatewayAddresses = array(); + + /** + * 等待连接个 gateway 地址 + * + * @var array + */ + protected $_waitingConnectGatewayAddresses = array(); + + /** + * Event::onConnect 回调 + * + * @var callback + */ + protected $_eventOnConnect = null; + + /** + * Event::onMessage 回调 + * + * @var callback + */ + protected $_eventOnMessage = null; + + /** + * Event::onClose 回调 + * + * @var callback + */ + protected $_eventOnClose = null; + + /** + * websocket回调 + * + * @var null + */ + protected $_eventOnWebSocketConnect = null; + + /** + * SESSION 版本缓存 + * + * @var array + */ + protected $_sessionVersion = array(); + + /** + * 用于保持长连接的心跳时间间隔 + * + * @var int + */ + const PERSISTENCE_CONNECTION_PING_INTERVAL = 25; + + /** + * 构造函数 + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', $context_option = array()) + { + parent::__construct($socket_name, $context_option); + $backrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backrace[0]['file']); + } + + /** + * {@inheritdoc} + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->_onWorkerReload = $this->onWorkerReload; + $this->_onWorkerStop = $this->onWorkerStop; + $this->onWorkerStop = array($this, 'onWorkerStop'); + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onWorkerReload = array($this, 'onWorkerReload'); + parent::run(); + } + + /** + * 当进程启动时一些初始化工作 + * + * @return void + */ + protected function onWorkerStart() + { + if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); + } + + if (!is_array($this->registerAddress)) { + $this->registerAddress = array($this->registerAddress); + } + $this->connectToRegister(); + + \GatewayWorker\Lib\Gateway::setBusinessWorker($this); + \GatewayWorker\Lib\Gateway::$secretKey = $this->secretKey; + if ($this->_onWorkerStart) { + call_user_func($this->_onWorkerStart, $this); + } + + if (is_callable($this->eventHandler . '::onWorkerStart')) { + call_user_func($this->eventHandler . '::onWorkerStart', $this); + } + + if (function_exists('pcntl_signal')) { + // 业务超时信号处理 + pcntl_signal(SIGALRM, array($this, 'timeoutHandler'), false); + } else { + $this->processTimeout = 0; + } + + // 设置回调 + if (is_callable($this->eventHandler . '::onConnect')) { + $this->_eventOnConnect = $this->eventHandler . '::onConnect'; + } + + if (is_callable($this->eventHandler . '::onMessage')) { + $this->_eventOnMessage = $this->eventHandler . '::onMessage'; + } else { + echo "Waring: {$this->eventHandler}::onMessage is not callable\n"; + } + + if (is_callable($this->eventHandler . '::onClose')) { + $this->_eventOnClose = $this->eventHandler . '::onClose'; + } + + if (is_callable($this->eventHandler . '::onWebSocketConnect')) { + $this->_eventOnWebSocketConnect = $this->eventHandler . '::onWebSocketConnect'; + } + + } + + /** + * onWorkerReload 回调 + * + * @param Worker $worker + */ + protected function onWorkerReload($worker) + { + // 防止进程立刻退出 + $worker->reloadable = false; + // 延迟 0.05 秒退出,避免 BusinessWorker 瞬间全部退出导致没有可用的 BusinessWorker 进程 + Timer::add(0.05, array('Workerman\Worker', 'stopAll')); + // 执行用户定义的 onWorkerReload 回调 + if ($this->_onWorkerReload) { + call_user_func($this->_onWorkerReload, $this); + } + } + + /** + * 当进程关闭时一些清理工作 + * + * @return void + */ + protected function onWorkerStop() + { + if ($this->_onWorkerStop) { + call_user_func($this->_onWorkerStop, $this); + } + if (is_callable($this->eventHandler . '::onWorkerStop')) { + call_user_func($this->eventHandler . '::onWorkerStop', $this); + } + } + + /** + * 连接服务注册中心 + * + * @return void + */ + public function connectToRegister() + { + foreach ($this->registerAddress as $register_address) { + $register_connection = new AsyncTcpConnection("text://{$register_address}"); + $secret_key = $this->secretKey; + $register_connection->onConnect = function () use ($register_connection, $secret_key, $register_address) { + $register_connection->send('{"event":"worker_connect","secret_key":"' . $secret_key . '"}'); + // 如果Register服务器不在本地服务器,则需要保持心跳 + if (strpos($register_address, '127.0.0.1') !== 0) { + $register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) { + $register_connection->send('{"event":"ping"}'); + }); + } + }; + $register_connection->onClose = function ($register_connection) { + if(!empty($register_connection->ping_timer)) { + Timer::del($register_connection->ping_timer); + } + $register_connection->reconnect(1); + }; + $register_connection->onMessage = array($this, 'onRegisterConnectionMessage'); + $register_connection->connect(); + } + } + + + /** + * 当注册中心发来消息时 + * + * @return void + */ + public function onRegisterConnectionMessage($register_connection, $data) + { + $data = json_decode($data, true); + if (!isset($data['event'])) { + echo "Received bad data from Register\n"; + return; + } + $event = $data['event']; + switch ($event) { + case 'broadcast_addresses': + if (!is_array($data['addresses'])) { + echo "Received bad data from Register. Addresses empty\n"; + return; + } + $addresses = $data['addresses']; + $this->_gatewayAddresses = array(); + foreach ($addresses as $addr) { + $this->_gatewayAddresses[$addr] = $addr; + } + $this->checkGatewayConnections($addresses); + break; + default: + echo "Receive bad event:$event from Register.\n"; + } + } + + /** + * 当 gateway 转发来数据时 + * + * @param TcpConnection $connection + * @param mixed $data + */ + public function onGatewayMessage($connection, $data) + { + $cmd = $data['cmd']; + if ($cmd === GatewayProtocol::CMD_PING) { + return; + } + // 上下文数据 + Context::$client_ip = $data['client_ip']; + Context::$client_port = $data['client_port']; + Context::$local_ip = $data['local_ip']; + Context::$local_port = $data['local_port']; + Context::$connection_id = $data['connection_id']; + Context::$client_id = Context::addressToClientId($data['local_ip'], $data['local_port'], + $data['connection_id']); + // $_SERVER 变量 + $_SERVER = array( + 'REMOTE_ADDR' => long2ip($data['client_ip']), + 'REMOTE_PORT' => $data['client_port'], + 'GATEWAY_ADDR' => long2ip($data['local_ip']), + 'GATEWAY_PORT' => $data['gateway_port'], + 'GATEWAY_CLIENT_ID' => Context::$client_id, + ); + // 检查session版本,如果是过期的session数据则拉取最新的数据 + if ($cmd !== GatewayProtocol::CMD_ON_CLOSE && isset($this->_sessionVersion[Context::$client_id]) && $this->_sessionVersion[Context::$client_id] !== crc32($data['ext_data'])) { + $_SESSION = Context::$old_session = \GatewayWorker\Lib\Gateway::getSession(Context::$client_id); + } else { + if (!isset($this->_sessionVersion[Context::$client_id])) { + $this->_sessionVersion[Context::$client_id] = crc32($data['ext_data']); + } + // 尝试解析 session + if ($data['ext_data'] != '') { + Context::$old_session = $_SESSION = Context::sessionDecode($data['ext_data']); + } else { + Context::$old_session = $_SESSION = null; + } + } + + if ($this->processTimeout) { + pcntl_alarm($this->processTimeout); + } + // 尝试执行 Event::onConnection、Event::onMessage、Event::onClose + switch ($cmd) { + case GatewayProtocol::CMD_ON_CONNECT: + if ($this->_eventOnConnect) { + call_user_func($this->_eventOnConnect, Context::$client_id); + } + break; + case GatewayProtocol::CMD_ON_MESSAGE: + if ($this->_eventOnMessage) { + call_user_func($this->_eventOnMessage, Context::$client_id, $data['body']); + } + break; + case GatewayProtocol::CMD_ON_CLOSE: + unset($this->_sessionVersion[Context::$client_id]); + if ($this->_eventOnClose) { + call_user_func($this->_eventOnClose, Context::$client_id); + } + break; + case GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT: + if ($this->_eventOnWebSocketConnect) { + call_user_func($this->_eventOnWebSocketConnect, Context::$client_id, $data['body']); + } + break; + } + if ($this->processTimeout) { + pcntl_alarm(0); + } + + // session 必须是数组 + if ($_SESSION !== null && !is_array($_SESSION)) { + throw new \Exception('$_SESSION must be an array. But $_SESSION=' . var_export($_SESSION, true) . ' is not array.'); + } + + // 判断 session 是否被更改 + if ($_SESSION !== Context::$old_session && $cmd !== GatewayProtocol::CMD_ON_CLOSE) { + $session_str_now = $_SESSION !== null ? Context::sessionEncode($_SESSION) : ''; + \GatewayWorker\Lib\Gateway::setSocketSession(Context::$client_id, $session_str_now); + $this->_sessionVersion[Context::$client_id] = crc32($session_str_now); + } + + Context::clear(); + } + + /** + * 当与 Gateway 的连接断开时触发 + * + * @param TcpConnection $connection + * @return void + */ + public function onGatewayClose($connection) + { + $addr = $connection->remoteAddress; + unset($this->gatewayConnections[$addr], $this->_connectingGatewayAddresses[$addr]); + if (isset($this->_gatewayAddresses[$addr]) && !isset($this->_waitingConnectGatewayAddresses[$addr])) { + Timer::add(1, array($this, 'tryToConnectGateway'), array($addr), false); + $this->_waitingConnectGatewayAddresses[$addr] = $addr; + } + } + + /** + * 尝试连接 Gateway 内部通讯地址 + * + * @param string $addr + */ + public function tryToConnectGateway($addr) + { + if (!isset($this->gatewayConnections[$addr]) && !isset($this->_connectingGatewayAddresses[$addr]) && isset($this->_gatewayAddresses[$addr])) { + $gateway_connection = new AsyncTcpConnection("GatewayProtocol://$addr"); + $gateway_connection->remoteAddress = $addr; + $gateway_connection->onConnect = array($this, 'onConnectGateway'); + $gateway_connection->onMessage = array($this, 'onGatewayMessage'); + $gateway_connection->onClose = array($this, 'onGatewayClose'); + $gateway_connection->onError = array($this, 'onGatewayError'); + $gateway_connection->maxSendBufferSize = $this->sendToGatewayBufferSize; + if (TcpConnection::$defaultMaxSendBufferSize == $gateway_connection->maxSendBufferSize) { + $gateway_connection->maxSendBufferSize = 50 * 1024 * 1024; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_WORKER_CONNECT; + $gateway_data['body'] = json_encode(array( + 'worker_key' =>"{$this->name}:{$this->id}", + 'secret_key' => $this->secretKey, + )); + $gateway_connection->send($gateway_data); + $gateway_connection->connect(); + $this->_connectingGatewayAddresses[$addr] = $addr; + } + unset($this->_waitingConnectGatewayAddresses[$addr]); + } + + /** + * 检查 gateway 的通信端口是否都已经连 + * 如果有未连接的端口,则尝试连接 + * + * @param array $addresses_list + */ + public function checkGatewayConnections($addresses_list) + { + if (empty($addresses_list)) { + return; + } + foreach ($addresses_list as $addr) { + if (!isset($this->_waitingConnectGatewayAddresses[$addr])) { + $this->tryToConnectGateway($addr); + } + } + } + + /** + * 当连接上 gateway 的通讯端口时触发 + * 将连接 connection 对象保存起来 + * + * @param TcpConnection $connection + * @return void + */ + public function onConnectGateway($connection) + { + $this->gatewayConnections[$connection->remoteAddress] = $connection; + unset($this->_connectingGatewayAddresses[$connection->remoteAddress], $this->_waitingConnectGatewayAddresses[$connection->remoteAddress]); + } + + /** + * 当与 gateway 的连接出现错误时触发 + * + * @param TcpConnection $connection + * @param int $error_no + * @param string $error_msg + */ + public function onGatewayError($connection, $error_no, $error_msg) + { + echo "GatewayConnection Error : $error_no ,$error_msg\n"; + } + + /** + * 获取所有 Gateway 内部通讯地址 + * + * @return array + */ + public function getAllGatewayAddresses() + { + return $this->_gatewayAddresses; + } + + /** + * 业务超时回调 + * + * @param int $signal + * @throws \Exception + */ + public function timeoutHandler($signal) + { + switch ($signal) { + // 超时时钟 + case SIGALRM: + // 超时异常 + $e = new \Exception("process_timeout", 506); + $trace_str = $e->getTraceAsString(); + // 去掉第一行timeoutHandler的调用栈 + $trace_str = $e->getMessage() . ":\n" . substr($trace_str, strpos($trace_str, "\n") + 1) . "\n"; + // 开发者没有设置超时处理函数,或者超时处理函数返回空则执行退出 + if (!$this->processTimeoutHandler || !call_user_func($this->processTimeoutHandler, $trace_str, $e)) { + Worker::stopAll(); + } + break; + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Gateway.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Gateway.php new file mode 100644 index 0000000..6153c36 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Gateway.php @@ -0,0 +1,1019 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use GatewayWorker\Lib\Context; + +use Workerman\Connection\TcpConnection; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Autoloader; +use Workerman\Connection\AsyncTcpConnection; +use GatewayWorker\Protocols\GatewayProtocol; + +/** + * + * Gateway,基于Worker 开发 + * 用于转发客户端的数据给Worker处理,以及转发Worker的数据给客户端 + * + * @author walkor + * + */ +class Gateway extends Worker +{ + /** + * 版本 + * + * @var string + */ + const VERSION = '3.0.12'; + + /** + * 本机 IP + * 单机部署默认 127.0.0.1,如果是分布式部署,需要设置成本机 IP + * + * @var string + */ + public $lanIp = '127.0.0.1'; + + /** + * 本机端口 + * + * @var string + */ + public $lanPort = 0; + + /** + * gateway 内部通讯起始端口,每个 gateway 实例应该都不同,步长1000 + * + * @var int + */ + public $startPort = 2000; + + /** + * 注册服务地址,用于注册 Gateway BusinessWorker,使之能够通讯 + * + * @var string|array + */ + public $registerAddress = '127.0.0.1:1236'; + + /** + * 是否可以平滑重启,gateway 不能平滑重启,否则会导致连接断开 + * + * @var bool + */ + public $reloadable = false; + + /** + * 心跳时间间隔 + * + * @var int + */ + public $pingInterval = 0; + + /** + * $pingNotResponseLimit * $pingInterval 时间内,客户端未发送任何数据,断开客户端连接 + * + * @var int + */ + public $pingNotResponseLimit = 0; + + /** + * 服务端向客户端发送的心跳数据 + * + * @var string + */ + public $pingData = ''; + + /** + * 秘钥 + * + * @var string + */ + public $secretKey = ''; + + /** + * 路由函数 + * + * @var callback + */ + public $router = null; + + + /** + * gateway进程转发给businessWorker进程的发送缓冲区大小 + * + * @var int + */ + public $sendToWorkerBufferSize = 10240000; + + /** + * gateway进程将数据发给客户端时每个客户端发送缓冲区大小 + * + * @var int + */ + public $sendToClientBufferSize = 1024000; + + /** + * 协议加速 + * + * @var bool + */ + public $protocolAccelerate = false; + + /** + * 保存客户端的所有 connection 对象 + * + * @var array + */ + protected $_clientConnections = array(); + + /** + * uid 到 connection 的映射,一对多关系 + */ + protected $_uidConnections = array(); + + /** + * group 到 connection 的映射,一对多关系 + * + * @var array + */ + protected $_groupConnections = array(); + + /** + * 保存所有 worker 的内部连接的 connection 对象 + * + * @var array + */ + protected $_workerConnections = array(); + + /** + * gateway 内部监听 worker 内部连接的 worker + * + * @var Worker + */ + protected $_innerTcpWorker = null; + + /** + * 当 worker 启动时 + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * 当有客户端连接时 + * + * @var callback + */ + protected $_onConnect = null; + + /** + * 当客户端发来消息时 + * + * @var callback + */ + protected $_onMessage = null; + + /** + * 当客户端连接关闭时 + * + * @var callback + */ + protected $_onClose = null; + + /** + * 当 worker 停止时 + * + * @var callback + */ + protected $_onWorkerStop = null; + + /** + * 进程启动时间 + * + * @var int + */ + protected $_startTime = 0; + + /** + * gateway 监听的端口 + * + * @var int + */ + protected $_gatewayPort = 0; + + /** + * connectionId 记录器 + * @var int + */ + protected static $_connectionIdRecorder = 0; + + /** + * 用于保持长连接的心跳时间间隔 + * + * @var int + */ + const PERSISTENCE_CONNECTION_PING_INTERVAL = 25; + + /** + * 构造函数 + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, $context_option = array()) + { + parent::__construct($socket_name, $context_option); + $this->_gatewayPort = substr(strrchr($socket_name,':'),1); + $this->router = array("\\GatewayWorker\\Gateway", 'routerBind'); + + $backtrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backtrace[0]['file']); + } + + /** + * {@inheritdoc} + */ + public function run() + { + // 保存用户的回调,当对应的事件发生时触发 + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + // 保存用户的回调,当对应的事件发生时触发 + $this->_onConnect = $this->onConnect; + $this->onConnect = array($this, 'onClientConnect'); + + // onMessage禁止用户设置回调 + $this->onMessage = array($this, 'onClientMessage'); + + // 保存用户的回调,当对应的事件发生时触发 + $this->_onClose = $this->onClose; + $this->onClose = array($this, 'onClientClose'); + // 保存用户的回调,当对应的事件发生时触发 + $this->_onWorkerStop = $this->onWorkerStop; + $this->onWorkerStop = array($this, 'onWorkerStop'); + + if (!is_array($this->registerAddress)) { + $this->registerAddress = array($this->registerAddress); + } + + // 记录进程启动的时间 + $this->_startTime = time(); + // 运行父方法 + parent::run(); + } + + /** + * 当客户端发来数据时,转发给worker处理 + * + * @param TcpConnection $connection + * @param mixed $data + */ + public function onClientMessage($connection, $data) + { + $connection->pingNotResponseCount = -1; + $this->sendToWorker(GatewayProtocol::CMD_ON_MESSAGE, $connection, $data); + } + + /** + * 当客户端连接上来时,初始化一些客户端的数据 + * 包括全局唯一的client_id、初始化session等 + * + * @param TcpConnection $connection + */ + public function onClientConnect($connection) + { + $connection->id = self::generateConnectionId(); + // 保存该连接的内部通讯的数据包报头,避免每次重新初始化 + $connection->gatewayHeader = array( + 'local_ip' => ip2long($this->lanIp), + 'local_port' => $this->lanPort, + 'client_ip' => ip2long($connection->getRemoteIp()), + 'client_port' => $connection->getRemotePort(), + 'gateway_port' => $this->_gatewayPort, + 'connection_id' => $connection->id, + 'flag' => 0, + ); + // 连接的 session + $connection->session = ''; + // 该连接的心跳参数 + $connection->pingNotResponseCount = -1; + // 该链接发送缓冲区大小 + $connection->maxSendBufferSize = $this->sendToClientBufferSize; + // 保存客户端连接 connection 对象 + $this->_clientConnections[$connection->id] = $connection; + + // 如果用户有自定义 onConnect 回调,则执行 + if ($this->_onConnect) { + call_user_func($this->_onConnect, $connection); + } elseif ($connection->protocol === '\Workerman\Protocols\Websocket') { + $connection->onWebSocketConnect = array($this, 'onWebsocketConnect'); + } + + $this->sendToWorker(GatewayProtocol::CMD_ON_CONNECT, $connection); + } + + /** + * websocket握手时触发 + * + * @param $connection + * @param $http_buffer + */ + public function onWebsocketConnect($connection, $http_buffer) + { + $this->sendToWorker(GatewayProtocol::CMD_ON_WEBSOCKET_CONNECT, $connection, array('get' => $_GET, 'server' => $_SERVER, 'cookie' => $_COOKIE)); + } + + /** + * 生成connection id + * @return int + */ + protected function generateConnectionId() + { + $max_unsigned_int = 4294967295; + if (self::$_connectionIdRecorder >= $max_unsigned_int) { + self::$_connectionIdRecorder = 0; + } + while(++self::$_connectionIdRecorder <= $max_unsigned_int) { + if(!isset($this->_clientConnections[self::$_connectionIdRecorder])) { + break; + } + } + return self::$_connectionIdRecorder; + } + + /** + * 发送数据给 worker 进程 + * + * @param int $cmd + * @param TcpConnection $connection + * @param mixed $body + * @return bool + */ + protected function sendToWorker($cmd, $connection, $body = '') + { + $gateway_data = $connection->gatewayHeader; + $gateway_data['cmd'] = $cmd; + $gateway_data['body'] = $body; + $gateway_data['ext_data'] = $connection->session; + if ($this->_workerConnections) { + // 调用路由函数,选择一个worker把请求转发给它 + /** @var TcpConnection $worker_connection */ + $worker_connection = call_user_func($this->router, $this->_workerConnections, $connection, $cmd, $body); + if (false === $worker_connection->send($gateway_data)) { + $msg = "SendBufferToWorker fail. May be the send buffer are overflow. See http://wiki.workerman.net/Error2"; + static::log($msg); + return false; + } + } // 没有可用的 worker + else { + // gateway 启动后 1-2 秒内 SendBufferToWorker fail 是正常现象,因为与 worker 的连接还没建立起来, + // 所以不记录日志,只是关闭连接 + $time_diff = 2; + if (time() - $this->_startTime >= $time_diff) { + $msg = 'SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://wiki.workerman.net/Error3'; + static::log($msg); + } + $connection->destroy(); + return false; + } + return true; + } + + /** + * 随机路由,返回 worker connection 对象 + * + * @param array $worker_connections + * @param TcpConnection $client_connection + * @param int $cmd + * @param mixed $buffer + * @return TcpConnection + */ + public static function routerRand($worker_connections, $client_connection, $cmd, $buffer) + { + return $worker_connections[array_rand($worker_connections)]; + } + + /** + * client_id 与 worker 绑定 + * + * @param array $worker_connections + * @param TcpConnection $client_connection + * @param int $cmd + * @param mixed $buffer + * @return TcpConnection + */ + public static function routerBind($worker_connections, $client_connection, $cmd, $buffer) + { + if (!isset($client_connection->businessworker_address) || !isset($worker_connections[$client_connection->businessworker_address])) { + $client_connection->businessworker_address = array_rand($worker_connections); + } + return $worker_connections[$client_connection->businessworker_address]; + } + + /** + * 当客户端关闭时 + * + * @param TcpConnection $connection + */ + public function onClientClose($connection) + { + // 尝试通知 worker,触发 Event::onClose + $this->sendToWorker(GatewayProtocol::CMD_ON_CLOSE, $connection); + unset($this->_clientConnections[$connection->id]); + // 清理 uid 数据 + if (!empty($connection->uid)) { + $uid = $connection->uid; + unset($this->_uidConnections[$uid][$connection->id]); + if (empty($this->_uidConnections[$uid])) { + unset($this->_uidConnections[$uid]); + } + } + // 清理 group 数据 + if (!empty($connection->groups)) { + foreach ($connection->groups as $group) { + unset($this->_groupConnections[$group][$connection->id]); + if (empty($this->_groupConnections[$group])) { + unset($this->_groupConnections[$group]); + } + } + } + // 触发 onClose + if ($this->_onClose) { + call_user_func($this->_onClose, $connection); + } + } + + /** + * 当 Gateway 启动的时候触发的回调函数 + * + * @return void + */ + public function onWorkerStart() + { + // 分配一个内部通讯端口 + $this->lanPort = $this->startPort + $this->id; + + // 如果有设置心跳,则定时执行 + if ($this->pingInterval > 0) { + $timer_interval = $this->pingNotResponseLimit > 0 ? $this->pingInterval / 2 : $this->pingInterval; + Timer::add($timer_interval, array($this, 'ping')); + } + + // 如果BusinessWorker ip不是127.0.0.1,则需要加gateway到BusinessWorker的心跳 + if ($this->lanIp !== '127.0.0.1') { + Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, array($this, 'pingBusinessWorker')); + } + + if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); + } + + // 初始化 gateway 内部的监听,用于监听 worker 的连接已经连接上发来的数据 + $this->_innerTcpWorker = new Worker("GatewayProtocol://{$this->lanIp}:{$this->lanPort}"); + $this->_innerTcpWorker->listen(); + $this->_innerTcpWorker->name = 'GatewayInnerWorker'; + + // 重新设置自动加载根目录 + Autoloader::setRootPath($this->_autoloadRootPath); + + // 设置内部监听的相关回调 + $this->_innerTcpWorker->onMessage = array($this, 'onWorkerMessage'); + + $this->_innerTcpWorker->onConnect = array($this, 'onWorkerConnect'); + $this->_innerTcpWorker->onClose = array($this, 'onWorkerClose'); + + // 注册 gateway 的内部通讯地址,worker 去连这个地址,以便 gateway 与 worker 之间建立起 TCP 长连接 + $this->registerAddress(); + + if ($this->_onWorkerStart) { + call_user_func($this->_onWorkerStart, $this); + } + } + + + /** + * 当 worker 通过内部通讯端口连接到 gateway 时 + * + * @param TcpConnection $connection + */ + public function onWorkerConnect($connection) + { + $connection->maxSendBufferSize = $this->sendToWorkerBufferSize; + $connection->authorized = $this->secretKey ? false : true; + } + + /** + * 当 worker 发来数据时 + * + * @param TcpConnection $connection + * @param mixed $data + * @throws \Exception + * + * @return void + */ + public function onWorkerMessage($connection, $data) + { + $cmd = $data['cmd']; + if (empty($connection->authorized) && $cmd !== GatewayProtocol::CMD_WORKER_CONNECT && $cmd !== GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT) { + self::log("Unauthorized request from " . $connection->getRemoteIp() . ":" . $connection->getRemotePort()); + $connection->close(); + return; + } + switch ($cmd) { + // BusinessWorker连接Gateway + case GatewayProtocol::CMD_WORKER_CONNECT: + $worker_info = json_decode($data['body'], true); + if ($worker_info['secret_key'] !== $this->secretKey) { + self::log("Gateway: Worker key does not match ".var_export($this->secretKey, true)." !== ". var_export($this->secretKey)); + $connection->close(); + return; + } + $key = $connection->getRemoteIp() . ':' . $worker_info['worker_key']; + // 在一台服务器上businessWorker->name不能相同 + if (isset($this->_workerConnections[$key])) { + self::log("Gateway: Worker->name conflict. Key:{$key}"); + $connection->close(); + return; + } + $connection->key = $key; + $this->_workerConnections[$key] = $connection; + $connection->authorized = true; + return; + // GatewayClient连接Gateway + case GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT: + $worker_info = json_decode($data['body'], true); + if ($worker_info['secret_key'] !== $this->secretKey) { + self::log("Gateway: GatewayClient key does not match ".var_export($this->secretKey, true)." !== ".var_export($this->secretKey, true)); + $connection->close(); + return; + } + $connection->authorized = true; + return; + // 向某客户端发送数据,Gateway::sendToClient($client_id, $message); + case GatewayProtocol::CMD_SEND_TO_ONE: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->send($data['body']); + } + return; + // 踢出用户,Gateway::closeClient($client_id, $message); + case GatewayProtocol::CMD_KICK: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->close($data['body']); + } + return; + // 立即销毁用户连接, Gateway::destroyClient($client_id); + case GatewayProtocol::CMD_DESTROY: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->destroy(); + } + return; + // 广播, Gateway::sendToAll($message, $client_id_array) + case GatewayProtocol::CMD_SEND_TO_ALL: + $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE); + $body = $data['body']; + if (!$raw && $this->protocolAccelerate && $this->protocol) { + $body = $this->preEncodeForClient($body); + $raw = true; + } + $ext_data = $data['ext_data'] ? json_decode($data['ext_data'], true) : ''; + // $client_id_array 不为空时,只广播给 $client_id_array 指定的客户端 + if (isset($ext_data['connections'])) { + foreach ($ext_data['connections'] as $connection_id) { + if (isset($this->_clientConnections[$connection_id])) { + $this->_clientConnections[$connection_id]->send($body, $raw); + } + } + } // $client_id_array 为空时,广播给所有在线客户端 + else { + $exclude_connection_id = !empty($ext_data['exclude']) ? $ext_data['exclude'] : null; + foreach ($this->_clientConnections as $client_connection) { + if (!isset($exclude_connection_id[$client_connection->id])) { + $client_connection->send($body, $raw); + } + } + } + return; + case GatewayProtocol::CMD_SELECT: + $client_info_array = array(); + $ext_data = json_decode($data['ext_data'], true); + if (!$ext_data) { + echo 'CMD_SELECT ext_data=' . var_export($data['ext_data'], true) . '\r\n'; + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + } + $fields = $ext_data['fields']; + $where = $ext_data['where']; + if ($where) { + $connection_box_map = array( + 'groups' => $this->_groupConnections, + 'uid' => $this->_uidConnections + ); + // $where = ['groups'=>[x,x..], 'uid'=>[x,x..], 'connection_id'=>[x,x..]] + foreach ($where as $key => $items) { + if ($key !== 'connection_id') { + $connections_box = $connection_box_map[$key]; + foreach ($items as $item) { + if (isset($connections_box[$item])) { + foreach ($connections_box[$item] as $connection_id => $client_connection) { + if (!isset($client_info_array[$connection_id])) { + $client_info_array[$connection_id] = array(); + // $fields = ['groups', 'uid', 'session'] + foreach ($fields as $field) { + $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null; + } + } + } + + } + } + } else { + foreach ($items as $connection_id) { + if (isset($this->_clientConnections[$connection_id])) { + $client_connection = $this->_clientConnections[$connection_id]; + $client_info_array[$connection_id] = array(); + // $fields = ['groups', 'uid', 'session'] + foreach ($fields as $field) { + $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null; + } + } + } + } + } + } else { + foreach ($this->_clientConnections as $connection_id => $client_connection) { + foreach ($fields as $field) { + $client_info_array[$connection_id][$field] = isset($client_connection->$field) ? $client_connection->$field : null; + } + } + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取在线群组列表 + case GatewayProtocol::CMD_GET_GROUP_ID_LIST: + $buffer = serialize(array_keys($this->_groupConnections)); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 重新赋值 session + case GatewayProtocol::CMD_SET_SESSION: + if (isset($this->_clientConnections[$data['connection_id']])) { + $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; + } + return; + // session合并 + case GatewayProtocol::CMD_UPDATE_SESSION: + if (!isset($this->_clientConnections[$data['connection_id']])) { + return; + } else { + if (!$this->_clientConnections[$data['connection_id']]->session) { + $this->_clientConnections[$data['connection_id']]->session = $data['ext_data']; + return; + } + $session = Context::sessionDecode($this->_clientConnections[$data['connection_id']]->session); + $session_for_merge = Context::sessionDecode($data['ext_data']); + $session = array_replace_recursive($session, $session_for_merge); + $this->_clientConnections[$data['connection_id']]->session = Context::sessionEncode($session); + } + return; + case GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID: + if (!isset($this->_clientConnections[$data['connection_id']])) { + $session = serialize(null); + } else { + if (!$this->_clientConnections[$data['connection_id']]->session) { + $session = serialize(array()); + } else { + $session = $this->_clientConnections[$data['connection_id']]->session; + } + } + $connection->send(pack('N', strlen($session)) . $session, true); + return; + // 获得客户端sessions + case GatewayProtocol::CMD_GET_ALL_CLIENT_SESSIONS: + $client_info_array = array(); + foreach ($this->_clientConnections as $connection_id => $client_connection) { + $client_info_array[$connection_id] = $client_connection->session; + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 判断某个 client_id 是否在线 Gateway::isOnline($client_id) + case GatewayProtocol::CMD_IS_ONLINE: + $buffer = serialize((int)isset($this->_clientConnections[$data['connection_id']])); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 将 client_id 与 uid 绑定 + case GatewayProtocol::CMD_BIND_UID: + $uid = $data['ext_data']; + if (empty($uid)) { + echo "bindUid(client_id, uid) uid empty, uid=" . var_export($uid, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (isset($client_connection->uid)) { + $current_uid = $client_connection->uid; + unset($this->_uidConnections[$current_uid][$connection_id]); + if (empty($this->_uidConnections[$current_uid])) { + unset($this->_uidConnections[$current_uid]); + } + } + $client_connection->uid = $uid; + $this->_uidConnections[$uid][$connection_id] = $client_connection; + return; + // client_id 与 uid 解绑 Gateway::unbindUid($client_id, $uid); + case GatewayProtocol::CMD_UNBIND_UID: + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (isset($client_connection->uid)) { + $current_uid = $client_connection->uid; + unset($this->_uidConnections[$current_uid][$connection_id]); + if (empty($this->_uidConnections[$current_uid])) { + unset($this->_uidConnections[$current_uid]); + } + $client_connection->uid_info = ''; + $client_connection->uid = null; + } + return; + // 发送数据给 uid Gateway::sendToUid($uid, $msg); + case GatewayProtocol::CMD_SEND_TO_UID: + $uid_array = json_decode($data['ext_data'], true); + foreach ($uid_array as $uid) { + if (!empty($this->_uidConnections[$uid])) { + foreach ($this->_uidConnections[$uid] as $connection) { + /** @var TcpConnection $connection */ + $connection->send($data['body']); + } + } + } + return; + // 将 $client_id 加入用户组 Gateway::joinGroup($client_id, $group); + case GatewayProtocol::CMD_JOIN_GROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "join(group) group empty, group=" . var_export($group, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (!isset($client_connection->groups)) { + $client_connection->groups = array(); + } + $client_connection->groups[$group] = $group; + $this->_groupConnections[$group][$connection_id] = $client_connection; + return; + // 将 $client_id 从某个用户组中移除 Gateway::leaveGroup($client_id, $group); + case GatewayProtocol::CMD_LEAVE_GROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "leave(group) group empty, group=" . var_export($group, true); + return; + } + $connection_id = $data['connection_id']; + if (!isset($this->_clientConnections[$connection_id])) { + return; + } + $client_connection = $this->_clientConnections[$connection_id]; + if (!isset($client_connection->groups[$group])) { + return; + } + unset($client_connection->groups[$group], $this->_groupConnections[$group][$connection_id]); + if (empty($this->_groupConnections[$group])) { + unset($this->_groupConnections[$group]); + } + return; + // 解散分组 + case GatewayProtocol::CMD_UNGROUP: + $group = $data['ext_data']; + if (empty($group)) { + echo "leave(group) group empty, group=" . var_export($group, true); + return; + } + if (empty($this->_groupConnections[$group])) { + return; + } + foreach ($this->_groupConnections[$group] as $client_connection) { + unset($client_connection->groups[$group]); + } + unset($this->_groupConnections[$group]); + return; + // 向某个用户组发送消息 Gateway::sendToGroup($group, $msg); + case GatewayProtocol::CMD_SEND_TO_GROUP: + $raw = (bool)($data['flag'] & GatewayProtocol::FLAG_NOT_CALL_ENCODE); + $body = $data['body']; + if (!$raw && $this->protocolAccelerate && $this->protocol) { + $body = $this->preEncodeForClient($body); + $raw = true; + } + $ext_data = json_decode($data['ext_data'], true); + $group_array = $ext_data['group']; + $exclude_connection_id = $ext_data['exclude']; + + foreach ($group_array as $group) { + if (!empty($this->_groupConnections[$group])) { + foreach ($this->_groupConnections[$group] as $connection) { + if(!isset($exclude_connection_id[$connection->id])) + { + /** @var TcpConnection $connection */ + $connection->send($body, $raw); + } + } + } + } + return; + // 获取某用户组成员信息 Gateway::getClientSessionsByGroup($group); + case GatewayProtocol::CMD_GET_CLIENT_SESSIONS_BY_GROUP: + $group = $data['ext_data']; + if (!isset($this->_groupConnections[$group])) { + $buffer = serialize(array()); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + } + $client_info_array = array(); + foreach ($this->_groupConnections[$group] as $connection_id => $client_connection) { + $client_info_array[$connection_id] = $client_connection->session; + } + $buffer = serialize($client_info_array); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取用户组成员数 Gateway::getClientCountByGroup($group); + case GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP: + $group = $data['ext_data']; + $count = 0; + if ($group !== '') { + if (isset($this->_groupConnections[$group])) { + $count = count($this->_groupConnections[$group]); + } + } else { + $count = count($this->_clientConnections); + } + $buffer = serialize($count); + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + // 获取与某个 uid 绑定的所有 client_id Gateway::getClientIdByUid($uid); + case GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID: + $uid = $data['ext_data']; + if (empty($this->_uidConnections[$uid])) { + $buffer = serialize(array()); + } else { + $buffer = serialize(array_keys($this->_uidConnections[$uid])); + } + $connection->send(pack('N', strlen($buffer)) . $buffer, true); + return; + default : + $err_msg = "gateway inner pack err cmd=$cmd"; + echo $err_msg; + } + } + + + /** + * 当worker连接关闭时 + * + * @param TcpConnection $connection + */ + public function onWorkerClose($connection) + { + if (isset($connection->key)) { + unset($this->_workerConnections[$connection->key]); + } + } + + /** + * 存储当前 Gateway 的内部通信地址 + * + * @return bool + */ + public function registerAddress() + { + $address = $this->lanIp . ':' . $this->lanPort; + foreach ($this->registerAddress as $register_address) { + $register_connection = new AsyncTcpConnection("text://{$register_address}"); + $secret_key = $this->secretKey; + $register_connection->onConnect = function($register_connection) use ($address, $secret_key, $register_address){ + $register_connection->send('{"event":"gateway_connect", "address":"' . $address . '", "secret_key":"' . $secret_key . '"}'); + // 如果Register服务器不在本地服务器,则需要保持心跳 + if (strpos($register_address, '127.0.0.1') !== 0) { + $register_connection->ping_timer = Timer::add(self::PERSISTENCE_CONNECTION_PING_INTERVAL, function () use ($register_connection) { + $register_connection->send('{"event":"ping"}'); + }); + } + }; + $register_connection->onClose = function ($register_connection) { + if(!empty($register_connection->ping_timer)) { + Timer::del($register_connection->ping_timer); + } + $register_connection->reconnect(1); + }; + $register_connection->connect(); + } + } + + + /** + * 心跳逻辑 + * + * @return void + */ + public function ping() + { + $ping_data = $this->pingData ? (string)$this->pingData : null; + $raw = false; + if ($this->protocolAccelerate && $ping_data && $this->protocol) { + $ping_data = $this->preEncodeForClient($ping_data); + $raw = true; + } + // 遍历所有客户端连接 + foreach ($this->_clientConnections as $connection) { + // 上次发送的心跳还没有回复次数大于限定值就断开 + if ($this->pingNotResponseLimit > 0 && + $connection->pingNotResponseCount >= $this->pingNotResponseLimit * 2 + ) { + $connection->destroy(); + continue; + } + // $connection->pingNotResponseCount 为 -1 说明最近客户端有发来消息,则不给客户端发送心跳 + $connection->pingNotResponseCount++; + if ($ping_data) { + if ($connection->pingNotResponseCount === 0 || + ($this->pingNotResponseLimit > 0 && $connection->pingNotResponseCount % 2 === 1) + ) { + continue; + } + $connection->send($ping_data, $raw); + } + } + } + + /** + * 向 BusinessWorker 发送心跳数据,用于保持长连接 + * + * @return void + */ + public function pingBusinessWorker() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_PING; + foreach ($this->_workerConnections as $connection) { + $connection->send($gateway_data); + } + } + + /** + * @param mixed $data + * + * @return string + */ + protected function preEncodeForClient($data) + { + foreach ($this->_clientConnections as $client_connection) { + return call_user_func(array($client_connection->protocol, 'encode'), $data, $client_connection); + } + } + + /** + * 当 gateway 关闭时触发,清理数据 + * + * @return void + */ + public function onWorkerStop() + { + // 尝试触发用户设置的回调 + if ($this->_onWorkerStop) { + call_user_func($this->_onWorkerStop, $this); + } + } + + /** + * Log. + * @param string $msg + */ + public static function log($msg){ + Timer::add(1, function() use ($msg) { + Worker::log($msg); + }, null, false); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php new file mode 100644 index 0000000..22ebccb --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Context.php @@ -0,0 +1,136 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Exception; + +/** + * 上下文 包含当前用户 uid, 内部通信 local_ip local_port socket_id,以及客户端 client_ip client_port + */ +class Context +{ + /** + * 内部通讯 id + * + * @var string + */ + public static $local_ip; + + /** + * 内部通讯端口 + * + * @var int + */ + public static $local_port; + + /** + * 客户端 ip + * + * @var string + */ + public static $client_ip; + + /** + * 客户端端口 + * + * @var int + */ + public static $client_port; + + /** + * client_id + * + * @var string + */ + public static $client_id; + + /** + * 连接 connection->id + * + * @var int + */ + public static $connection_id; + + /** + * 旧的session + * + * @var string + */ + public static $old_session; + + /** + * 编码 session + * + * @param mixed $session_data + * @return string + */ + public static function sessionEncode($session_data = '') + { + if ($session_data !== '') { + return serialize($session_data); + } + return ''; + } + + /** + * 解码 session + * + * @param string $session_buffer + * @return mixed + */ + public static function sessionDecode($session_buffer) + { + return unserialize($session_buffer); + } + + /** + * 清除上下文 + * + * @return void + */ + public static function clear() + { + self::$local_ip = self::$local_port = self::$client_ip = self::$client_port = + self::$client_id = self::$connection_id = self::$old_session = null; + } + + /** + * 通讯地址到 client_id 的转换 + * + * @param int $local_ip + * @param int $local_port + * @param int $connection_id + * @return string + */ + public static function addressToClientId($local_ip, $local_port, $connection_id) + { + return bin2hex(pack('NnN', $local_ip, $local_port, $connection_id)); + } + + /** + * client_id 到通讯地址的转换 + * + * @param string $client_id + * @return array + * @throws Exception + */ + public static function clientIdToAddress($client_id) + { + if (strlen($client_id) !== 20) { + echo new Exception("client_id $client_id is invalid"); + return false; + } + return unpack('Nlocal_ip/nlocal_port/Nconnection_id', pack('H*', $client_id)); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php new file mode 100644 index 0000000..9f0e4b6 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Db.php @@ -0,0 +1,76 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Config\Db as DbConfig; +use Exception; + +/** + * 数据库类 + */ +class Db +{ + /** + * 实例数组 + * + * @var array + */ + protected static $instance = array(); + + /** + * 获取实例 + * + * @param string $config_name + * @return DbConnection + * @throws Exception + */ + public static function instance($config_name) + { + if (!isset(DbConfig::$$config_name)) { + echo "\\Config\\Db::$config_name not set\n"; + throw new Exception("\\Config\\Db::$config_name not set\n"); + } + + if (empty(self::$instance[$config_name])) { + $config = DbConfig::$$config_name; + self::$instance[$config_name] = new DbConnection($config['host'], $config['port'], + $config['user'], $config['password'], $config['dbname'],$config['charset']); + } + return self::$instance[$config_name]; + } + + /** + * 关闭数据库实例 + * + * @param string $config_name + */ + public static function close($config_name) + { + if (isset(self::$instance[$config_name])) { + self::$instance[$config_name]->closeConnection(); + self::$instance[$config_name] = null; + } + } + + /** + * 关闭所有数据库实例 + */ + public static function closeAll() + { + foreach (self::$instance as $connection) { + $connection->closeConnection(); + } + self::$instance = array(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php new file mode 100644 index 0000000..df6daff --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/DbConnection.php @@ -0,0 +1,1976 @@ +type = 'SELECT'; + if (!is_array($cols)) { + $cols = array($cols); + } + $this->cols($cols); + return $this; + } + + /** + * 从哪个表删除 + * + * @param string $table + * @return self + */ + public function delete($table) + { + $this->type = 'DELETE'; + $this->table = $this->quoteName($table); + $this->fromRaw($this->quoteName($table)); + return $this; + } + + /** + * 更新哪个表 + * + * @param string $table + * @return self + */ + public function update($table) + { + $this->type = 'UPDATE'; + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 向哪个表插入 + * + * @param string $table + * @return self + */ + public function insert($table) + { + $this->type = 'INSERT'; + $this->table = $this->quoteName($table); + return $this; + } + + /** + * + * 设置 SQL_CALC_FOUND_ROWS 标记. + * + * @param bool $enable + * @return self + */ + public function calcFoundRows($enable = true) + { + $this->setFlag('SQL_CALC_FOUND_ROWS', $enable); + return $this; + } + + /** + * 设置 SQL_CACHE 标记 + * + * @param bool $enable + * @return self + */ + public function cache($enable = true) + { + $this->setFlag('SQL_CACHE', $enable); + return $this; + } + + /** + * 设置 SQL_NO_CACHE 标记 + * + * @param bool $enable + * @return self + */ + public function noCache($enable = true) + { + $this->setFlag('SQL_NO_CACHE', $enable); + return $this; + } + + /** + * 设置 STRAIGHT_JOIN 标记. + * + * @param bool $enable + * @return self + */ + public function straightJoin($enable = true) + { + $this->setFlag('STRAIGHT_JOIN', $enable); + return $this; + } + + /** + * 设置 HIGH_PRIORITY 标记 + * + * @param bool $enable + * @return self + */ + public function highPriority($enable = true) + { + $this->setFlag('HIGH_PRIORITY', $enable); + return $this; + } + + /** + * 设置 SQL_SMALL_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function smallResult($enable = true) + { + $this->setFlag('SQL_SMALL_RESULT', $enable); + return $this; + } + + /** + * 设置 SQL_BIG_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function bigResult($enable = true) + { + $this->setFlag('SQL_BIG_RESULT', $enable); + return $this; + } + + /** + * 设置 SQL_BUFFER_RESULT 标记 + * + * @param bool $enable + * @return self + */ + public function bufferResult($enable = true) + { + $this->setFlag('SQL_BUFFER_RESULT', $enable); + return $this; + } + + /** + * 设置 FOR UPDATE 标记 + * + * @param bool $enable + * @return self + */ + public function forUpdate($enable = true) + { + $this->for_update = (bool)$enable; + return $this; + } + + /** + * 设置 DISTINCT 标记 + * + * @param bool $enable + * @return self + */ + public function distinct($enable = true) + { + $this->setFlag('DISTINCT', $enable); + return $this; + } + + /** + * 设置 LOW_PRIORITY 标记 + * + * @param bool $enable + * @return self + */ + public function lowPriority($enable = true) + { + $this->setFlag('LOW_PRIORITY', $enable); + return $this; + } + + /** + * 设置 IGNORE 标记 + * + * @param bool $enable + * @return self + */ + public function ignore($enable = true) + { + $this->setFlag('IGNORE', $enable); + return $this; + } + + /** + * 设置 QUICK 标记 + * + * @param bool $enable + * @return self + */ + public function quick($enable = true) + { + $this->setFlag('QUICK', $enable); + return $this; + } + + /** + * 设置 DELAYED 标记 + * + * @param bool $enable + * @return self + */ + public function delayed($enable = true) + { + $this->setFlag('DELAYED', $enable); + return $this; + } + + /** + * 序列化 + * + * @return string + */ + public function __toString() + { + $union = ''; + if ($this->union) { + $union = implode(' ', $this->union) . ' '; + } + return $union . $this->build(); + } + + /** + * 设置每页多少条记录 + * + * @param int $paging + * @return self + */ + public function setPaging($paging) + { + $this->paging = (int)$paging; + return $this; + } + + /** + * 获取每页多少条记录 + * + * @return int + */ + public function getPaging() + { + return $this->paging; + } + + /** + * 获取绑定在占位符上的值 + */ + public function getBindValues() + { + switch ($this->type) { + case 'SELECT': + return $this->getBindValuesSELECT(); + case 'DELETE': + case 'UPDATE': + case 'INSERT': + return $this->getBindValuesCOMMON(); + default : + throw new Exception("type err"); + } + } + + /** + * 获取绑定在占位符上的值 + * + * @return array + */ + public function getBindValuesSELECT() + { + $bind_values = $this->bind_values; + $i = 1; + foreach ($this->bind_where as $val) { + $bind_values[$i] = $val; + $i++; + } + foreach ($this->bind_having as $val) { + $bind_values[$i] = $val; + $i++; + } + return $bind_values; + } + + /** + * + * SELECT选择哪些列 + * + * @param mixed $key + * @param string $val + * @return void + */ + protected function addColSELECT($key, $val) + { + if (is_string($key)) { + $this->cols[$val] = $key; + } else { + $this->addColWithAlias($val); + } + } + + /** + * SELECT 增加选择的列 + * + * @param string $spec + */ + protected function addColWithAlias($spec) + { + $parts = explode(' ', $spec); + $count = count($parts); + if ($count == 2) { + $this->cols[$parts[1]] = $parts[0]; + } elseif ($count == 3 && strtoupper($parts[1]) == 'AS') { + $this->cols[$parts[2]] = $parts[0]; + } else { + $this->cols[] = $spec; + } + } + + /** + * from 哪个表 + * + * @param string $table + * @return self + */ + public function from($table) + { + return $this->fromRaw($this->quoteName($table)); + } + + /** + * from的表 + * + * @param string $table + * @return self + */ + public function fromRaw($table) + { + $this->from[] = array($table); + $this->from_key++; + return $this; + } + + /** + * + * 子查询 + * + * @param string $table + * @param string $name The alias name for the sub-select. + * @return self + */ + public function fromSubSelect($table, $name) + { + $this->from[] = array("($table) AS " . $this->quoteName($name)); + $this->from_key++; + return $this; + } + + + /** + * 增加 join 语句 + * + * @param string $table + * @param string $cond + * @param string $type + * @return self + * @throws Exception + */ + public function join($table, $cond = null, $type = '') + { + return $this->joinInternal($type, $table, $cond); + } + + /** + * 增加 join 语句 + * + * @param string $join inner, left, natural + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + protected function joinInternal($join, $table, $cond = null) + { + if (!$this->from) { + throw new Exception('Cannot join() without from()'); + } + + $join = strtoupper(ltrim("$join JOIN")); + $table = $this->quoteName($table); + $cond = $this->fixJoinCondition($cond); + $this->from[$this->from_key][] = rtrim("$join $table $cond"); + return $this; + } + + /** + * quote + * + * @param string $cond + * @return string + * + */ + protected function fixJoinCondition($cond) + { + if (!$cond) { + return ''; + } + + $cond = $this->quoteNamesIn($cond); + + if (strtoupper(substr(ltrim($cond), 0, 3)) == 'ON ') { + return $cond; + } + + if (strtoupper(substr(ltrim($cond), 0, 6)) == 'USING ') { + return $cond; + } + + return 'ON ' . $cond; + } + + /** + * inner join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function innerJoin($table, $cond = null) + { + return $this->joinInternal('INNER', $table, $cond); + } + + /** + * left join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function leftJoin($table, $cond = null) + { + return $this->joinInternal('LEFT', $table, $cond); + } + + /** + * right join + * + * @param string $table + * @param string $cond + * @return self + * @throws Exception + */ + public function rightJoin($table, $cond = null) + { + return $this->joinInternal('RIGHT', $table, $cond); + } + + /** + * joinSubSelect + * + * @param string $join inner, left, natural + * @param string $spec + * @param string $name sub-select 的别名 + * @param string $cond + * @return self + * @throws Exception + */ + public function joinSubSelect($join, $spec, $name, $cond = null) + { + if (!$this->from) { + throw new \Exception('Cannot join() without from() first.'); + } + + $join = strtoupper(ltrim("$join JOIN")); + $name = $this->quoteName($name); + $cond = $this->fixJoinCondition($cond); + $this->from[$this->from_key][] = rtrim("$join ($spec) AS $name $cond"); + return $this; + } + + /** + * group by 语句 + * + * @param array $cols + * @return self + */ + public function groupBy(array $cols) + { + foreach ($cols as $col) { + $this->group_by[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * having 语句 + * + * @param string $cond + * @return self + */ + public function having($cond) + { + $this->addClauseCondWithBind('having', 'AND', func_get_args()); + return $this; + } + + /** + * or having 语句 + * + * @param string $cond The HAVING condition. + * @return self + */ + public function orHaving($cond) + { + $this->addClauseCondWithBind('having', 'OR', func_get_args()); + return $this; + } + + /** + * 设置每页的记录数量 + * + * @param int $page + * @return self + */ + public function page($page) + { + $this->limit = 0; + $this->offset = 0; + + $page = (int)$page; + if ($page > 0) { + $this->limit = $this->paging; + $this->offset = $this->paging * ($page - 1); + } + return $this; + } + + /** + * union + * + * @return self + */ + public function union() + { + $this->union[] = $this->build() . ' UNION'; + $this->reset(); + return $this; + } + + /** + * unionAll + * + * @return self + */ + public function unionAll() + { + $this->union[] = $this->build() . ' UNION ALL'; + $this->reset(); + return $this; + } + + /** + * 重置 + */ + protected function reset() + { + $this->resetFlags(); + $this->cols = array(); + $this->from = array(); + $this->from_key = -1; + $this->where = array(); + $this->group_by = array(); + $this->having = array(); + $this->order_by = array(); + $this->limit = 0; + $this->offset = 0; + $this->for_update = false; + } + + /** + * 清除所有数据 + */ + protected function resetAll() + { + $this->union = array(); + $this->for_update = false; + $this->cols = array(); + $this->from = array(); + $this->from_key = -1; + $this->group_by = array(); + $this->having = array(); + $this->bind_having = array(); + $this->paging = 10; + $this->bind_values = array(); + $this->where = array(); + $this->bind_where = array(); + $this->order_by = array(); + $this->limit = 0; + $this->offset = 0; + $this->flags = array(); + $this->table = ''; + $this->last_insert_id_names = array(); + $this->col_values = array(); + $this->returning = array(); + $this->parameters = array(); + } + + /** + * 创建 SELECT SQL + * + * @return string + */ + protected function buildSELECT() + { + return 'SELECT' + . $this->buildFlags() + . $this->buildCols() + . $this->buildFrom() + . $this->buildWhere() + . $this->buildGroupBy() + . $this->buildHaving() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildForUpdate(); + } + + /** + * 创建 DELETE SQL + */ + protected function buildDELETE() + { + return 'DELETE' + . $this->buildFlags() + . $this->buildFrom() + . $this->buildWhere() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildReturning(); + } + + /** + * 生成 SELECT 列语句 + * + * @return string + * @throws Exception + */ + protected function buildCols() + { + if (!$this->cols) { + throw new Exception('No columns in the SELECT.'); + } + + $cols = array(); + foreach ($this->cols as $key => $val) { + if (is_int($key)) { + $cols[] = $this->quoteNamesIn($val); + } else { + $cols[] = $this->quoteNamesIn("$val AS $key"); + } + } + + return $this->indentCsv($cols); + } + + /** + * 生成 FROM 语句. + * + * @return string + */ + protected function buildFrom() + { + if (!$this->from) { + return ''; + } + + $refs = array(); + foreach ($this->from as $from) { + $refs[] = implode(' ', $from); + } + return ' FROM' . $this->indentCsv($refs); + } + + /** + * 生成 GROUP BY 语句. + * + * @return string + */ + protected function buildGroupBy() + { + if (!$this->group_by) { + return ''; + } + return ' GROUP BY' . $this->indentCsv($this->group_by); + } + + /** + * 生成 HAVING 语句. + * + * @return string + */ + protected function buildHaving() + { + if (!$this->having) { + return ''; + } + return ' HAVING' . $this->indent($this->having); + } + + /** + * 生成 FOR UPDATE 语句 + * + * @return string + */ + protected function buildForUpdate() + { + if (!$this->for_update) { + return ''; + } + return ' FOR UPDATE'; + } + + /** + * where + * + * @param string|array $cond + * @return self + */ + public function where($cond) + { + if (is_array($cond)) { + foreach ($cond as $key => $val) { + if (is_string($key)) { + $this->addWhere('AND', array($key, $val)); + } else { + $this->addWhere('AND', array($val)); + } + } + } else { + $this->addWhere('AND', func_get_args()); + } + return $this; + } + + /** + * or where + * + * @param string|array $cond + * @return self + */ + public function orWhere($cond) + { + if (is_array($cond)) { + foreach ($cond as $key => $val) { + if (is_string($key)) { + $this->addWhere('OR', array($key, $val)); + } else { + $this->addWhere('OR', array($val)); + } + } + } else { + $this->addWhere('OR', func_get_args()); + } + return $this; + } + + /** + * limit + * + * @param int $limit + * @return self + */ + public function limit($limit) + { + $this->limit = (int)$limit; + return $this; + } + + /** + * limit offset + * + * @param int $offset + * @return self + */ + public function offset($offset) + { + $this->offset = (int)$offset; + return $this; + } + + /** + * orderby. + * + * @param array $cols + * @return self + */ + public function orderBy(array $cols) + { + return $this->addOrderBy($cols); + } + + /** + * order by ASC OR DESC + * + * @param array $cols + * @param bool $order_asc + * @return self + */ + public function orderByASC(array $cols, $order_asc = true) + { + $this->order_asc = $order_asc; + return $this->addOrderBy($cols); + } + + /** + * order by DESC + * + * @param array $cols + * @return self + */ + public function orderByDESC(array $cols) + { + $this->order_asc = false; + return $this->addOrderBy($cols); + } + + // -------------abstractquery---------- + /** + * 返回逗号分隔的字符串 + * + * @param array $list + * @return string + */ + protected function indentCsv(array $list) + { + return ' ' . implode(',', $list); + } + + /** + * 返回空格分隔的字符串 + * + * @param array $list + * @return string + */ + protected function indent(array $list) + { + return ' ' . implode(' ', $list); + } + + /** + * 批量为占位符绑定值 + * + * @param array $bind_values + * @return self + * + */ + public function bindValues(array $bind_values) + { + foreach ($bind_values as $key => $val) { + $this->bindValue($key, $val); + } + return $this; + } + + /** + * 单个为占位符绑定值 + * + * @param string $name + * @param mixed $value + * @return self + */ + public function bindValue($name, $value) + { + $this->bind_values[$name] = $value; + return $this; + } + + /** + * 生成 flag + * + * @return string + */ + protected function buildFlags() + { + if (!$this->flags) { + return ''; + } + return ' ' . implode(' ', array_keys($this->flags)); + } + + /** + * 设置 flag. + * + * @param string $flag + * @param bool $enable + */ + protected function setFlag($flag, $enable = true) + { + if ($enable) { + $this->flags[$flag] = true; + } else { + unset($this->flags[$flag]); + } + } + + /** + * 重置 flag + */ + protected function resetFlags() + { + $this->flags = array(); + } + + /** + * + * 添加 where 语句 + * + * @param string $andor 'AND' or 'OR + * @param array $conditions + * @return self + * + */ + protected function addWhere($andor, $conditions) + { + $this->addClauseCondWithBind('where', $andor, $conditions); + return $this; + } + + /** + * 添加条件和绑定值 + * + * @param string $clause where 、having等 + * @param string $andor AND、OR等 + * @param array $conditions + */ + protected function addClauseCondWithBind($clause, $andor, $conditions) + { + $cond = array_shift($conditions); + $cond = $this->quoteNamesIn($cond); + + $bind =& $this->{"bind_{$clause}"}; + foreach ($conditions as $value) { + $bind[] = $value; + } + + $clause =& $this->$clause; + if ($clause) { + $clause[] = "$andor $cond"; + } else { + $clause[] = $cond; + } + } + + /** + * 生成 where 语句 + * + * @return string + */ + protected function buildWhere() + { + if (!$this->where) { + return ''; + } + return ' WHERE' . $this->indent($this->where); + } + + /** + * 增加 order by + * + * @param array $spec The columns and direction to order by. + * @return self + */ + protected function addOrderBy(array $spec) + { + foreach ($spec as $col) { + $this->order_by[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * 生成 order by 语句 + * + * @return string + */ + protected function buildOrderBy() + { + if (!$this->order_by) { + return ''; + } + + if ($this->order_asc) { + return ' ORDER BY' . $this->indentCsv($this->order_by) . ' ASC'; + } else { + return ' ORDER BY' . $this->indentCsv($this->order_by) . ' DESC'; + } + } + + /** + * 生成 limit 语句 + * + * @return string + */ + protected function buildLimit() + { + $has_limit = $this->type == 'DELETE' || $this->type == 'UPDATE'; + $has_offset = $this->type == 'SELECT'; + + if ($has_offset && $this->limit) { + $clause = " LIMIT {$this->limit}"; + if ($this->offset) { + $clause .= " OFFSET {$this->offset}"; + } + return $clause; + } elseif ($has_limit && $this->limit) { + return " LIMIT {$this->limit}"; + } + return ''; + } + + /** + * Quotes + * + * @param string $spec + * @return string|array + */ + public function quoteName($spec) + { + $spec = trim($spec); + $seps = array(' AS ', ' ', '.'); + foreach ($seps as $sep) { + $pos = strripos($spec, $sep); + if ($pos) { + return $this->quoteNameWithSeparator($spec, $sep, $pos); + } + } + return $this->replaceName($spec); + } + + /** + * 指定分隔符的 Quotes + * + * @param string $spec + * @param string $sep + * @param int $pos + * @return string + */ + protected function quoteNameWithSeparator($spec, $sep, $pos) + { + $len = strlen($sep); + $part1 = $this->quoteName(substr($spec, 0, $pos)); + $part2 = $this->replaceName(substr($spec, $pos + $len)); + return "{$part1}{$sep}{$part2}"; + } + + /** + * Quotes "table.col" 格式的字符串 + * + * @param string $text + * @return string|array + */ + public function quoteNamesIn($text) + { + $list = $this->getListForQuoteNamesIn($text); + $last = count($list) - 1; + $text = null; + foreach ($list as $key => $val) { + if (($key + 1) % 3) { + $text .= $this->quoteNamesInLoop($val, $key == $last); + } + } + return $text; + } + + /** + * 返回 quote 元素列表 + * + * @param string $text + * @return array + */ + protected function getListForQuoteNamesIn($text) + { + $apos = "'"; + $quot = '"'; + return preg_split( + "/(($apos+|$quot+|\\$apos+|\\$quot+).*?\\2)/", + $text, + -1, + PREG_SPLIT_DELIM_CAPTURE + ); + } + + /** + * 循环 quote + * + * @param string $val + * @param bool $is_last + * @return string + */ + protected function quoteNamesInLoop($val, $is_last) + { + if ($is_last) { + return $this->replaceNamesAndAliasIn($val); + } + return $this->replaceNamesIn($val); + } + + /** + * 替换成别名 + * + * @param string $val + * @return string + */ + protected function replaceNamesAndAliasIn($val) + { + $quoted = $this->replaceNamesIn($val); + $pos = strripos($quoted, ' AS '); + if ($pos) { + $alias = $this->replaceName(substr($quoted, $pos + 4)); + $quoted = substr($quoted, 0, $pos) . " AS $alias"; + } + return $quoted; + } + + /** + * Quotes name + * + * @param string $name + * @return string + */ + protected function replaceName($name) + { + $name = trim($name); + if ($name == '*') { + return $name; + } + return '`' . $name . '`'; + } + + /** + * Quotes + * + * @param string $text + * @return string|array + */ + protected function replaceNamesIn($text) + { + $is_string_literal = strpos($text, "'") !== false + || strpos($text, '"') !== false; + if ($is_string_literal) { + return $text; + } + + $word = '[a-z_][a-z0-9_]+'; + + $find = "/(\\b)($word)\\.($word)(\\b)/i"; + + $repl = '$1`$2`.`$3`$4'; + + $text = preg_replace($find, $repl, $text); + + return $text; + } + + // ---------- insert -------------- + /** + * 设置 `table.column` 与 last-insert-id 的映射 + * + * @param array $last_insert_id_names + */ + public function setLastInsertIdNames(array $last_insert_id_names) + { + $this->last_insert_id_names = $last_insert_id_names; + } + + /** + * insert into. + * + * @param string $table + * @return self + */ + public function into($table) + { + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 生成 INSERT 语句 + * + * @return string + */ + protected function buildINSERT() + { + return 'INSERT' + . $this->buildFlags() + . $this->buildInto() + . $this->buildValuesForInsert() + . $this->buildReturning(); + } + + /** + * 生成 INTO 语句 + * + * @return string + */ + protected function buildInto() + { + return " INTO " . $this->table; + } + + /** + * PDO::lastInsertId() + * + * @param string $col + * @return mixed + */ + public function getLastInsertIdName($col) + { + $key = str_replace('`', '', $this->table) . '.' . $col; + if (isset($this->last_insert_id_names[$key])) { + return $this->last_insert_id_names[$key]; + } + + return null; + } + + /** + * 设置一列,如果有第二各参数,则把第二个参数绑定在占位符上 + * + * @param string $col + * @return self + */ + public function col($col) + { + return call_user_func_array(array($this, 'addCol'), func_get_args()); + } + + /** + * 设置多列 + * + * @param array $cols + * @return self + */ + public function cols(array $cols) + { + if ($this->type == 'SELECT') { + foreach ($cols as $key => $val) { + $this->addColSELECT($key, $val); + } + return $this; + } + return $this->addCols($cols); + } + + /** + * 直接设置列的值 + * + * @param string $col + * @param string $value + * @return self + */ + public function set($col, $value) + { + return $this->setCol($col, $value); + } + + /** + * 为 INSERT 语句绑定值 + * + * @return string + */ + protected function buildValuesForInsert() + { + return ' (' . $this->indentCsv(array_keys($this->col_values)) . ') VALUES (' . + $this->indentCsv(array_values($this->col_values)) . ')'; + } + + // ------update------- + /** + * 更新哪个表 + * + * @param string $table + * @return self + */ + public function table($table) + { + $this->table = $this->quoteName($table); + return $this; + } + + /** + * 生成完整 SQL 语句 + * + * @return string + * @throws Exception + */ + protected function build() + { + switch ($this->type) { + case 'DELETE': + return $this->buildDELETE(); + case 'INSERT': + return $this->buildINSERT(); + case 'UPDATE': + return $this->buildUPDATE(); + case 'SELECT': + return $this->buildSELECT(); + } + throw new Exception("type empty"); + } + + /** + * 生成更新的 SQL 语句 + */ + protected function buildUPDATE() + { + return 'UPDATE' + . $this->buildFlags() + . $this->buildTable() + . $this->buildValuesForUpdate() + . $this->buildWhere() + . $this->buildOrderBy() + . $this->buildLimit() + . $this->buildReturning(); + } + + /** + * 哪个表 + * + * @return string + */ + protected function buildTable() + { + return " {$this->table}"; + } + + /** + * 为更新语句绑定值 + * + * @return string + */ + protected function buildValuesForUpdate() + { + $values = array(); + foreach ($this->col_values as $col => $value) { + $values[] = "{$col} = {$value}"; + } + return ' SET' . $this->indentCsv($values); + } + + // ----------Dml--------------- + /** + * 获取绑定的值 + * + * @return array + */ + public function getBindValuesCOMMON() + { + $bind_values = $this->bind_values; + $i = 1; + foreach ($this->bind_where as $val) { + $bind_values[$i] = $val; + $i++; + } + return $bind_values; + } + + /** + * 设置列 + * + * @param string $col + * @return self + */ + protected function addCol($col) + { + $key = $this->quoteName($col); + $this->col_values[$key] = ":$col"; + $args = func_get_args(); + if (count($args) > 1) { + $this->bindValue($col, $args[1]); + } + return $this; + } + + /** + * 设置多个列 + * + * @param array $cols + * @return self + */ + protected function addCols(array $cols) + { + foreach ($cols as $key => $val) { + if (is_int($key)) { + $this->addCol($val); + } else { + $this->addCol($key, $val); + } + } + return $this; + } + + /** + * 设置单列的值 + * + * @param string $col . + * @param string $value + * @return self + */ + protected function setCol($col, $value) + { + if ($value === null) { + $value = 'NULL'; + } + + $key = $this->quoteName($col); + $value = $this->quoteNamesIn($value); + $this->col_values[$key] = $value; + return $this; + } + + /** + * 增加返回的列 + * + * @param array $cols + * @return self + * + */ + protected function addReturning(array $cols) + { + foreach ($cols as $col) { + $this->returning[] = $this->quoteNamesIn($col); + } + return $this; + } + + /** + * 生成 RETURNING 语句 + * + * @return string + */ + protected function buildReturning() + { + if (!$this->returning) { + return ''; + } + return ' RETURNING' . $this->indentCsv($this->returning); + } + + /** + * 构造函数 + * + * @param string $host + * @param int $port + * @param string $user + * @param string $password + * @param string $db_name + * @param string $charset + */ + public function __construct($host, $port, $user, $password, $db_name, $charset = 'utf8') + { + $this->settings = array( + 'host' => $host, + 'port' => $port, + 'user' => $user, + 'password' => $password, + 'dbname' => $db_name, + 'charset' => $charset, + ); + $this->connect(); + } + + /** + * 创建 PDO 实例 + */ + protected function connect() + { + $dsn = 'mysql:dbname=' . $this->settings["dbname"] . ';host=' . + $this->settings["host"] . ';port=' . $this->settings['port']; + $this->pdo = new PDO($dsn, $this->settings["user"], $this->settings["password"], + array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES ' . (!empty($this->settings['charset']) ? + $this->settings['charset'] : 'utf8') + )); + $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); + $this->pdo->setAttribute(PDO::ATTR_STRINGIFY_FETCHES, false); + $this->pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false); + } + + /** + * 关闭连接 + */ + public function closeConnection() + { + $this->pdo = null; + } + + /** + * 执行 + * + * @param string $query + * @param string $parameters + * @throws PDOException + */ + protected function execute($query, $parameters = "") + { + try { + $this->sQuery = @$this->pdo->prepare($query); + $this->bindMore($parameters); + if (!empty($this->parameters)) { + foreach ($this->parameters as $param) { + $parameters = explode("\x7F", $param); + $this->sQuery->bindParam($parameters[0], $parameters[1]); + } + } + $this->success = $this->sQuery->execute(); + } catch (PDOException $e) { + // 服务端断开时重连一次 + if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) { + $this->closeConnection(); + $this->connect(); + + try { + $this->sQuery = $this->pdo->prepare($query); + $this->bindMore($parameters); + if (!empty($this->parameters)) { + foreach ($this->parameters as $param) { + $parameters = explode("\x7F", $param); + $this->sQuery->bindParam($parameters[0], $parameters[1]); + } + } + $this->success = $this->sQuery->execute(); + } catch (PDOException $ex) { + $this->rollBackTrans(); + throw $ex; + } + } else { + $this->rollBackTrans(); + $msg = $e->getMessage(); + $err_msg = "SQL:".$this->lastSQL()." ".$msg; + $exception = new \PDOException($err_msg, (int)$e->getCode()); + throw $exception; + } + } + $this->parameters = array(); + } + + /** + * 绑定 + * + * @param string $para + * @param string $value + */ + public function bind($para, $value) + { + if (is_string($para)) { + $this->parameters[sizeof($this->parameters)] = ":" . $para . "\x7F" . $value; + } else { + $this->parameters[sizeof($this->parameters)] = $para . "\x7F" . $value; + } + } + + /** + * 绑定多个 + * + * @param array $parray + */ + public function bindMore($parray) + { + if (empty($this->parameters) && is_array($parray)) { + $columns = array_keys($parray); + foreach ($columns as $i => &$column) { + $this->bind($column, $parray[$column]); + } + } + } + + /** + * 执行 SQL + * + * @param string $query + * @param array $params + * @param int $fetchmode + * @return mixed + */ + public function query($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + + $rawStatement = explode(" ", $query); + + $statement = strtolower(trim($rawStatement[0])); + if ($statement === 'select' || $statement === 'show') { + return $this->sQuery->fetchAll($fetchmode); + } elseif ($statement === 'update' || $statement === 'delete') { + return $this->sQuery->rowCount(); + } elseif ($statement === 'insert') { + if ($this->sQuery->rowCount() > 0) { + return $this->lastInsertId(); + } + } else { + return null; + } + + return null; + } + + /** + * 返回一列 + * + * @param string $query + * @param array $params + * @return array + */ + public function column($query = '', $params = null) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + $columns = $this->sQuery->fetchAll(PDO::FETCH_NUM); + $column = null; + foreach ($columns as $cells) { + $column[] = $cells[0]; + } + return $column; + } + + /** + * 返回一行 + * + * @param string $query + * @param array $params + * @param int $fetchmode + * @return array + */ + public function row($query = '', $params = null, $fetchmode = PDO::FETCH_ASSOC) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + return $this->sQuery->fetch($fetchmode); + } + + /** + * 返回单个值 + * + * @param string $query + * @param array $params + * @return string + */ + public function single($query = '', $params = null) + { + $query = trim($query); + if (empty($query)) { + $query = $this->build(); + if (!$params) { + $params = $this->getBindValues(); + } + } + + $this->resetAll(); + $this->lastSql = $query; + + $this->execute($query, $params); + return $this->sQuery->fetchColumn(); + } + + /** + * 返回 lastInsertId + * + * @return string + */ + public function lastInsertId() + { + return $this->pdo->lastInsertId(); + } + + /** + * 返回最后一条执行的 sql + * + * @return string + */ + public function lastSQL() + { + return $this->lastSql; + } + + /** + * 开始事务 + */ + public function beginTrans() + { + try { + $this->pdo->beginTransaction(); + } catch (PDOException $e) { + // 服务端断开时重连一次 + if ($e->errorInfo[1] == 2006 || $e->errorInfo[1] == 2013) { + $this->pdo->beginTransaction(); + } else { + throw $e; + } + } + } + + /** + * 提交事务 + */ + public function commitTrans() + { + $this->pdo->commit(); + } + + /** + * 事务回滚 + */ + public function rollBackTrans() + { + if ($this->pdo->inTransaction()) { + $this->pdo->rollBack(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php new file mode 100644 index 0000000..f0a5c10 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Lib/Gateway.php @@ -0,0 +1,1361 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Lib; + +use Exception; +use GatewayWorker\Protocols\GatewayProtocol; +use Workerman\Connection\TcpConnection; + +/** + * 数据发送相关 + */ +class Gateway +{ + /** + * gateway 实例 + * + * @var object + */ + protected static $businessWorker = null; + + /** + * 注册中心地址 + * + * @var string|array + */ + public static $registerAddress = '127.0.0.1:1236'; + + /** + * 秘钥 + * @var string + */ + public static $secretKey = ''; + + /** + * 链接超时时间 + * @var int + */ + public static $connectTimeout = 3; + + /** + * 与Gateway是否是长链接 + * @var bool + */ + public static $persistentConnection = true; + + /** + * 向所有客户端连接(或者 client_id_array 指定的客户端连接)广播消息 + * + * @param string $message 向客户端发送的消息 + * @param array $client_id_array 客户端 id 数组 + * @param array $exclude_client_id 不给这些client_id发 + * @param bool $raw 是否发送原始数据(即不调用gateway的协议的encode方法) + * @return void + * @throws Exception + */ + public static function sendToAll($message, $client_id_array = null, $exclude_client_id = null, $raw = false) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_ALL; + $gateway_data['body'] = $message; + if ($raw) { + $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE; + } + + if ($exclude_client_id) { + if (!is_array($exclude_client_id)) { + $exclude_client_id = array($exclude_client_id); + } + if ($client_id_array) { + $exclude_client_id = array_flip($exclude_client_id); + } + } + + if ($client_id_array) { + if (!is_array($client_id_array)) { + echo new \Exception('bad $client_id_array:'.var_export($client_id_array, true)); + return; + } + $data_array = array(); + foreach ($client_id_array as $client_id) { + if (isset($exclude_client_id[$client_id])) { + continue; + } + $address = Context::clientIdToAddress($client_id); + if ($address) { + $key = long2ip($address['local_ip']) . ":{$address['local_port']}"; + $data_array[$key][$address['connection_id']] = $address['connection_id']; + } + } + foreach ($data_array as $addr => $connection_id_list) { + $the_gateway_data = $gateway_data; + $the_gateway_data['ext_data'] = json_encode(array('connections' => $connection_id_list)); + static::sendToGateway($addr, $the_gateway_data); + } + return; + } elseif (empty($client_id_array) && is_array($client_id_array)) { + return; + } + + if (!$exclude_client_id) { + return static::sendToAllGateway($gateway_data); + } + + $address_connection_array = static::clientIdArrayToAddressArray($exclude_client_id); + + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (static::$businessWorker) { + foreach (static::$businessWorker->gatewayConnections as $address => $gateway_connection) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('exclude'=> $address_connection_array[$address])) : ''; + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($gateway_data); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = static::getAllGatewayAddressesFromRegister(); + foreach ($all_addresses as $address) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('exclude'=> $address_connection_array[$address])) : ''; + static::sendToGateway($address, $gateway_data); + } + } + + } + + /** + * 向某个client_id对应的连接发消息 + * + * @param int $client_id + * @param string $message + * @return void + */ + public static function sendToClient($client_id, $message) + { + return static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SEND_TO_ONE, $message); + } + + /** + * 向当前客户端连接发送消息 + * + * @param string $message + * @return bool + */ + public static function sendToCurrentClient($message) + { + return static::sendCmdAndMessageToClient(null, GatewayProtocol::CMD_SEND_TO_ONE, $message); + } + + /** + * 判断某个uid是否在线 + * + * @param string $uid + * @return int 0|1 + */ + public static function isUidOnline($uid) + { + return (int)static::getClientIdByUid($uid); + } + + /** + * 判断client_id对应的连接是否在线 + * + * @param int $client_id + * @return int 0|1 + */ + public static function isOnline($client_id) + { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return 0; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (isset(static::$businessWorker)) { + if (!isset(static::$businessWorker->gatewayConnections[$address])) { + return 0; + } + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_IS_ONLINE; + $gateway_data['connection_id'] = $address_data['connection_id']; + return (int)static::sendAndRecv($address, $gateway_data); + } + + /** + * 获取所有在线用户的session,client_id为 key(弃用,请用getAllClientSessions代替) + * + * @param string $group + * @return array + */ + public static function getAllClientInfo($group = '') + { + echo "Warning: Gateway::getAllClientInfo is deprecated and will be removed in a future, please use Gateway::getAllClientSessions instead."; + return static::getAllClientSessions($group); + } + + /** + * 获取所有在线client_id的session,client_id为 key + * + * @param string $group + * @return array + */ + public static function getAllClientSessions($group = '') + { + $gateway_data = GatewayProtocol::$empty; + if (!$group) { + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_ALL_CLIENT_SESSIONS; + } else { + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_SESSIONS_BY_GROUP; + $gateway_data['ext_data'] = $group; + } + $status_data = array(); + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $data) { + if ($data) { + foreach ($data as $connection_id => $session_buffer) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + if ($client_id === Context::$client_id) { + $status_data[$client_id] = (array)$_SESSION; + } else { + $status_data[$client_id] = $session_buffer ? Context::sessionDecode($session_buffer) : array(); + } + } + } + } + } + return $status_data; + } + + /** + * 获取某个组的连接信息(弃用,请用getClientSessionsByGroup代替) + * + * @param string $group + * @return array + */ + public static function getClientInfoByGroup($group) + { + echo "Warning: Gateway::getClientInfoByGroup is deprecated and will be removed in a future, please use Gateway::getClientSessionsByGroup instead."; + return static::getAllClientSessions($group); + } + + /** + * 获取某个组的所有client_id的session信息 + * + * @param string $group + * + * @return array + */ + public static function getClientSessionsByGroup($group) + { + if (static::isValidGroupId($group)) { + return static::getAllClientSessions($group); + } + return array(); + } + + /** + * 获取所有在线client_id数 + * + * @return int + */ + public static function getAllClientIdCount() + { + return static::getClientCountByGroup(); + } + + /** + * 获取所有在线client_id数(getAllClientIdCount的别名) + * + * @return int + */ + public static function getAllClientCount() + { + return static::getAllClientIdCount(); + } + + /** + * 获取某个组的在线client_id数 + * + * @param string $group + * @return int + */ + public static function getClientIdCountByGroup($group = '') + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_COUNT_BY_GROUP; + $gateway_data['ext_data'] = $group; + $total_count = 0; + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $count) { + if ($count) { + $total_count += $count; + } + } + } + return $total_count; + } + + /** + * getClientIdCountByGroup 函数的别名 + * + * @param string $group + * @return int + */ + public static function getClientCountByGroup($group = '') + { + return static::getClientIdCountByGroup($group); + } + + /** + * 获取某个群组在线client_id列表 + * + * @param string $group + * @return array + */ + public static function getClientIdListByGroup($group) + { + if (!static::isValidGroupId($group)) { + return array(); + } + + $data = static::select(array('uid'), array('groups' => is_array($group) ? $group : array($group))); + $client_id_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + $client_id_map[$client_id] = $client_id; + } + } + } + return $client_id_map; + } + + /** + * 获取集群所有在线client_id列表 + * + * @return array + */ + public static function getAllClientIdList() + { + return static::formatClientIdFromGatewayBuffer(static::select(array('uid'))); + } + + /** + * 格式化client_id + * + * @param $data + * @return array + */ + protected static function formatClientIdFromGatewayBuffer($data) + { + $client_id_list = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + $client_id_list[$client_id] = $client_id; + } + } + } + return $client_id_list; + } + + + /** + * 获取与 uid 绑定的 client_id 列表 + * + * @param string $uid + * @return array + */ + public static function getClientIdByUid($uid) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_CLIENT_ID_BY_UID; + $gateway_data['ext_data'] = $uid; + $client_list = array(); + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $connection_id_array) { + if ($connection_id_array) { + foreach ($connection_id_array as $connection_id) { + $client_list[] = Context::addressToClientId($local_ip, $local_port, $connection_id); + } + } + } + } + return $client_list; + } + + /** + * 获取某个群组在线uid列表 + * + * @param string $group + * @return array + */ + public static function getUidListByGroup($group) + { + if (!static::isValidGroupId($group)) { + return array(); + } + + $group = is_array($group) ? $group : array($group); + $data = static::select(array('uid'), array('groups' => $group)); + $uid_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (!empty($info['uid'])) { + $uid_map[$info['uid']] = $info['uid']; + } + } + } + } + return $uid_map; + } + + /** + * 获取某个群组在线uid数 + * + * @param string $group + * @return int + */ + public static function getUidCountByGroup($group) + { + if (static::isValidGroupId($group)) { + return count(static::getUidListByGroup($group)); + } + return 0; + } + + /** + * 获取全局在线uid列表 + * + * @return array + */ + public static function getAllUidList() + { + $data = static::select(array('uid')); + $uid_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (!empty($info['uid'])) { + $uid_map[$info['uid']] = $info['uid']; + } + } + } + } + return $uid_map; + } + + /** + * 获取全局在线uid数 + * @return int + */ + public static function getAllUidCount() + { + return count(static::getAllUidList()); + } + + /** + * 通过client_id获取uid + * + * @param $client_id + * @return mixed + */ + public static function getUidByClientId($client_id) + { + $data = static::select(array('uid'), array('client_id'=>array($client_id))); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $info) { + return $info['uid']; + } + } + } + } + + /** + * 获取所有在线的群组id + * + * @return array + */ + public static function getAllGroupIdList() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_GROUP_ID_LIST; + $group_id_list = array(); + $all_buffer_array = static::getBufferFromAllGateway($gateway_data); + foreach ($all_buffer_array as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $group_id_array) { + if (is_array($group_id_array)) { + foreach ($group_id_array as $group_id) { + if (!isset($group_id_list[$group_id])) { + $group_id_list[$group_id] = $group_id; + } + } + } + } + } + return $group_id_list; + } + + + /** + * 获取所有在线分组的uid数量,也就是每个分组的在线用户数 + * + * @return array + */ + public static function getAllGroupUidCount() + { + $group_uid_map = static::getAllGroupUidList(); + $group_uid_count_map = array(); + foreach ($group_uid_map as $group_id => $uid_list) { + $group_uid_count_map[$group_id] = count($uid_list); + } + return $group_uid_count_map; + } + + + + /** + * 获取所有分组uid在线列表 + * + * @return array + */ + public static function getAllGroupUidList() + { + $data = static::select(array('uid','groups')); + $group_uid_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (empty($info['uid']) || empty($info['groups'])) { + break; + } + $uid = $info['uid']; + foreach ($info['groups'] as $group_id) { + if(!isset($group_uid_map[$group_id])) { + $group_uid_map[$group_id] = array(); + } + $group_uid_map[$group_id][$uid] = $uid; + } + } + } + } + return $group_uid_map; + } + + /** + * 获取所有群组在线client_id列表 + * + * @return array + */ + public static function getAllGroupClientIdList() + { + $data = static::select(array('groups')); + $group_client_id_map = array(); + foreach ($data as $local_ip => $buffer_array) { + foreach ($buffer_array as $local_port => $items) { + //$items = ['connection_id'=>['uid'=>x, 'group'=>[x,x..], 'session'=>[..]], 'client_id'=>[..], ..]; + foreach ($items as $connection_id => $info) { + if (empty($info['groups'])) { + break; + } + $client_id = Context::addressToClientId($local_ip, $local_port, $connection_id); + foreach ($info['groups'] as $group_id) { + if(!isset($group_client_id_map[$group_id])) { + $group_client_id_map[$group_id] = array(); + } + $group_client_id_map[$group_id][$client_id] = $client_id; + } + } + } + } + return $group_client_id_map; + } + + /** + * 获取所有群组在线client_id数量,也就是获取每个群组在线连接数 + * + * @return array + */ + public static function getAllGroupClientIdCount() + { + $group_client_map = static::getAllGroupClientIdList(); + $group_client_count_map = array(); + foreach ($group_client_map as $group_id => $client_id_list) { + $group_client_count_map[$group_id] = count($client_id_list); + } + return $group_client_count_map; + } + + + /** + * 根据条件到gateway搜索数据 + * + * @param array $fields + * @param array $where + * @return array + */ + protected static function select($fields = array('session','uid','groups'), $where = array()) + { + $t = microtime(true); + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SELECT; + $gateway_data['ext_data'] = array('fields' => $fields, 'where' => $where); + $gateway_data_list = array(); + // 有client_id,能计算出需要和哪些gateway通讯,只和必要的gateway通讯能降低系统负载 + if (isset($where['client_id'])) { + $client_id_list = $where['client_id']; + unset($gateway_data['ext_data']['where']['client_id']); + $gateway_data['ext_data']['where']['connection_id'] = array(); + foreach ($client_id_list as $client_id) { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + continue; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (!isset($gateway_data_list[$address])) { + $gateway_data_list[$address] = $gateway_data; + } + $gateway_data_list[$address]['ext_data']['where']['connection_id'][$address_data['connection_id']] = $address_data['connection_id']; + } + foreach ($gateway_data_list as $address => $item) { + $gateway_data_list[$address]['ext_data'] = json_encode($item['ext_data']); + } + // 有其它条件,则还是需要向所有gateway发送 + if (count($where) !== 1) { + $gateway_data['ext_data'] = json_encode($gateway_data['ext_data']); + foreach (static::getAllGatewayAddress() as $address) { + if (!isset($gateway_data_list[$address])) { + $gateway_data_list[$address] = $gateway_data; + } + } + } + $data = static::getBufferFromSomeGateway($gateway_data_list); + } else { + $gateway_data['ext_data'] = json_encode($gateway_data['ext_data']); + $data = static::getBufferFromAllGateway($gateway_data); + } + + return $data; + } + + /** + * 生成验证包,用于验证此客户端的合法性 + * + * @return string + */ + protected static function generateAuthBuffer() + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GATEWAY_CLIENT_CONNECT; + $gateway_data['body'] = json_encode(array( + 'secret_key' => static::$secretKey, + )); + return GatewayProtocol::encode($gateway_data); + } + + /** + * 批量向某些gateway发包,并得到返回数组 + * + * @param array $gateway_data_array + * @return array + * @throws Exception + */ + protected static function getBufferFromSomeGateway($gateway_data_array) + { + $gateway_buffer_array = array(); + $auth_buffer = static::$secretKey ? static::generateAuthBuffer() : ''; + foreach ($gateway_data_array as $address => $gateway_data) { + if ($auth_buffer) { + $gateway_buffer_array[$address] = $auth_buffer.GatewayProtocol::encode($gateway_data); + } else { + $gateway_buffer_array[$address] = GatewayProtocol::encode($gateway_data); + } + } + return static::getBufferFromGateway($gateway_buffer_array); + } + + /** + * 批量向所有 gateway 发包,并得到返回数组 + * + * @param string $gateway_data + * @return array + * @throws Exception + */ + protected static function getBufferFromAllGateway($gateway_data) + { + $addresses = static::getAllGatewayAddress(); + $gateway_buffer_array = array(); + $gateway_buffer = GatewayProtocol::encode($gateway_data); + $gateway_buffer = static::$secretKey ? static::generateAuthBuffer() . $gateway_buffer : $gateway_buffer; + foreach ($addresses as $address) { + $gateway_buffer_array[$address] = $gateway_buffer; + } + + return static::getBufferFromGateway($gateway_buffer_array); + } + + /** + * 获取所有gateway内部通讯地址 + * + * @return array + * @throws Exception + */ + protected static function getAllGatewayAddress() + { + if (isset(static::$businessWorker)) { + $addresses = static::$businessWorker->getAllGatewayAddresses(); + if (empty($addresses)) { + throw new Exception('businessWorker::getAllGatewayAddresses return empty'); + } + } else { + $addresses = static::getAllGatewayAddressesFromRegister(); + if (empty($addresses)) { + return array(); + } + } + return $addresses; + } + + /** + * 批量向gateway发送并获取数据 + * @param $gateway_buffer_array + * @return array + */ + protected static function getBufferFromGateway($gateway_buffer_array) + { + $client_array = $status_data = $client_address_map = $receive_buffer_array = $recv_length_array = array(); + // 批量向所有gateway进程发送请求数据 + foreach ($gateway_buffer_array as $address => $gateway_buffer) { + $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout); + if ($client && strlen($gateway_buffer) === stream_socket_sendto($client, $gateway_buffer)) { + $socket_id = (int)$client; + $client_array[$socket_id] = $client; + $client_address_map[$socket_id] = explode(':', $address); + $receive_buffer_array[$socket_id] = ''; + } + } + // 超时5秒 + $timeout = 5; + $time_start = microtime(true); + // 批量接收请求 + while (count($client_array) > 0) { + $write = $except = array(); + $read = $client_array; + if (@stream_select($read, $write, $except, $timeout)) { + foreach ($read as $client) { + $socket_id = (int)$client; + $buffer = stream_socket_recvfrom($client, 65535); + if ($buffer !== '' && $buffer !== false) { + $receive_buffer_array[$socket_id] .= $buffer; + $receive_length = strlen($receive_buffer_array[$socket_id]); + if (empty($recv_length_array[$socket_id]) && $receive_length >= 4) { + $recv_length_array[$socket_id] = current(unpack('N', $receive_buffer_array[$socket_id])); + } + if (!empty($recv_length_array[$socket_id]) && $receive_length >= $recv_length_array[$socket_id] + 4) { + unset($client_array[$socket_id]); + } + } elseif (feof($client)) { + unset($client_array[$socket_id]); + } + } + } + if (microtime(true) - $time_start > $timeout) { + break; + } + } + $format_buffer_array = array(); + foreach ($receive_buffer_array as $socket_id => $buffer) { + $local_ip = ip2long($client_address_map[$socket_id][0]); + $local_port = $client_address_map[$socket_id][1]; + $format_buffer_array[$local_ip][$local_port] = unserialize(substr($buffer, 4)); + } + return $format_buffer_array; + } + + /** + * 踢掉某个客户端,并以$message通知被踢掉客户端 + * + * @param int $client_id + * @param string $message + * @return void + */ + public static function closeClient($client_id, $message = null) + { + if ($client_id === Context::$client_id) { + return static::closeCurrentClient($message); + } // 不是发给当前用户则使用存储中的地址 + else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + return static::kickAddress($address, $address_data['connection_id'], $message); + } + } + + /** + * 踢掉当前客户端,并以$message通知被踢掉客户端 + * + * @param string $message + * @return bool + * @throws Exception + */ + public static function closeCurrentClient($message = null) + { + if (!Context::$connection_id) { + throw new Exception('closeCurrentClient can not be called in async context'); + } + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + return static::kickAddress($address, Context::$connection_id, $message); + } + + /** + * 踢掉某个客户端并直接立即销毁相关连接 + * + * @param int $client_id + * @return bool + */ + public static function destoryClient($client_id) + { + if ($client_id === Context::$client_id) { + return static::destoryCurrentClient(); + } // 不是发给当前用户则使用存储中的地址 + else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + return static::destroyAddress($address, $address_data['connection_id']); + } + } + + /** + * 踢掉当前客户端并直接立即销毁相关连接 + * + * @return bool + * @throws Exception + */ + public static function destoryCurrentClient() + { + if (!Context::$connection_id) { + throw new Exception('destoryCurrentClient can not be called in async context'); + } + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + return static::destroyAddress($address, Context::$connection_id); + } + + /** + * 将 client_id 与 uid 绑定 + * + * @param int $client_id + * @param int|string $uid + * @return void + */ + public static function bindUid($client_id, $uid) + { + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_BIND_UID, '', $uid); + } + + /** + * 将 client_id 与 uid 解除绑定 + * + * @param int $client_id + * @param int|string $uid + * @return void + */ + public static function unbindUid($client_id, $uid) + { + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UNBIND_UID, '', $uid); + } + + /** + * 将 client_id 加入组 + * + * @param int $client_id + * @param int|string $group + * @return void + */ + public static function joinGroup($client_id, $group) + { + + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_JOIN_GROUP, '', $group); + } + + /** + * 将 client_id 离开组 + * + * @param int $client_id + * @param int|string $group + * + * @return void + */ + public static function leaveGroup($client_id, $group) + { + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_LEAVE_GROUP, '', $group); + } + + /** + * 取消分组 + * + * @param int|string $group + * + * @return void + */ + public static function ungroup($group) + { + if (!static::isValidGroupId($group)) { + return false; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_UNGROUP; + $gateway_data['ext_data'] = $group; + return static::sendToAllGateway($gateway_data); + + } + + /** + * 向所有 uid 发送 + * + * @param int|string|array $uid + * @param string $message + * + * @return void + */ + public static function sendToUid($uid, $message) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_UID; + $gateway_data['body'] = $message; + + if (!is_array($uid)) { + $uid = array($uid); + } + + $gateway_data['ext_data'] = json_encode($uid); + + static::sendToAllGateway($gateway_data); + } + + /** + * 向 group 发送 + * + * @param int|string|array $group 组(不允许是 0 '0' false null array()等为空的值) + * @param string $message 消息 + * @param array $exclude_client_id 不给这些client_id发 + * @param bool $raw 发送原始数据(即不调用gateway的协议的encode方法) + * + * @return void + */ + public static function sendToGroup($group, $message, $exclude_client_id = null, $raw = false) + { + if (!static::isValidGroupId($group)) { + return false; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_SEND_TO_GROUP; + $gateway_data['body'] = $message; + if ($raw) { + $gateway_data['flag'] |= GatewayProtocol::FLAG_NOT_CALL_ENCODE; + } + + if (!is_array($group)) { + $group = array($group); + } + + // 分组发送,没有排除的client_id,直接发送 + $default_ext_data_buffer = json_encode(array('group'=> $group, 'exclude'=> null)); + if (empty($exclude_client_id)) { + $gateway_data['ext_data'] = $default_ext_data_buffer; + return static::sendToAllGateway($gateway_data); + } + + // 分组发送,有排除的client_id,需要将client_id转换成对应gateway进程内的connectionId + if (!is_array($exclude_client_id)) { + $exclude_client_id = array($exclude_client_id); + } + + $address_connection_array = static::clientIdArrayToAddressArray($exclude_client_id); + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (static::$businessWorker) { + foreach (static::$businessWorker->gatewayConnections as $address => $gateway_connection) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) : + $default_ext_data_buffer; + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($gateway_data); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $addresses = static::getAllGatewayAddressesFromRegister(); + foreach ($addresses as $address) { + $gateway_data['ext_data'] = isset($address_connection_array[$address]) ? + json_encode(array('group'=> $group, 'exclude'=> $address_connection_array[$address])) : + $default_ext_data_buffer; + static::sendToGateway($address, $gateway_data); + } + } + } + + /** + * 更新 session,框架自动调用,开发者不要调用 + * + * @param int $client_id + * @param string $session_str + * @return bool + */ + public static function setSocketSession($client_id, $session_str) + { + return static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_SET_SESSION, '', $session_str); + } + + /** + * 设置 session,原session值会被覆盖 + * + * @param int $client_id + * @param array $session + * + * @return void + */ + public static function setSession($client_id, array $session) + { + if (Context::$client_id === $client_id) { + $_SESSION = $session; + Context::$old_session = $_SESSION; + } + static::setSocketSession($client_id, Context::sessionEncode($session)); + } + + /** + * 更新 session,实际上是与老的session合并 + * + * @param int $client_id + * @param array $session + * + * @return void + */ + public static function updateSession($client_id, array $session) + { + if (Context::$client_id === $client_id) { + $_SESSION = array_replace_recursive((array)$_SESSION, $session); + Context::$old_session = $_SESSION; + } + static::sendCmdAndMessageToClient($client_id, GatewayProtocol::CMD_UPDATE_SESSION, '', Context::sessionEncode($session)); + } + + /** + * 获取某个client_id的session + * + * @param int $client_id + * @return mixed false表示出错、null表示用户不存在、array表示具体的session信息 + */ + public static function getSession($client_id) + { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + if (isset(static::$businessWorker)) { + if (!isset(static::$businessWorker->gatewayConnections[$address])) { + return null; + } + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_GET_SESSION_BY_CLIENT_ID; + $gateway_data['connection_id'] = $address_data['connection_id']; + return static::sendAndRecv($address, $gateway_data); + } + + /** + * 向某个用户网关发送命令和消息 + * + * @param int $client_id + * @param int $cmd + * @param string $message + * @param string $ext_data + * @return boolean + */ + protected static function sendCmdAndMessageToClient($client_id, $cmd, $message, $ext_data = '') + { + // 如果是发给当前用户则直接获取上下文中的地址 + if ($client_id === Context::$client_id || $client_id === null) { + $address = long2ip(Context::$local_ip) . ':' . Context::$local_port; + $connection_id = Context::$connection_id; + } else { + $address_data = Context::clientIdToAddress($client_id); + if (!$address_data) { + return false; + } + $address = long2ip($address_data['local_ip']) . ":{$address_data['local_port']}"; + $connection_id = $address_data['connection_id']; + } + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = $cmd; + $gateway_data['connection_id'] = $connection_id; + $gateway_data['body'] = $message; + if (!empty($ext_data)) { + $gateway_data['ext_data'] = $ext_data; + } + + return static::sendToGateway($address, $gateway_data); + } + + /** + * 发送数据并返回 + * + * @param int $address + * @param mixed $data + * @return bool + * @throws Exception + */ + protected static function sendAndRecv($address, $data) + { + $buffer = GatewayProtocol::encode($data); + $buffer = static::$secretKey ? static::generateAuthBuffer() . $buffer : $buffer; + $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout); + if (!$client) { + throw new Exception("can not connect to tcp://$address $errmsg"); + } + if (strlen($buffer) === stream_socket_sendto($client, $buffer)) { + $timeout = 5; + // 阻塞读 + stream_set_blocking($client, 1); + // 1秒超时 + stream_set_timeout($client, 1); + $all_buffer = ''; + $time_start = microtime(true); + $pack_len = 0; + while (1) { + $buf = stream_socket_recvfrom($client, 655350); + if ($buf !== '' && $buf !== false) { + $all_buffer .= $buf; + } else { + if (feof($client)) { + throw new Exception("connection close tcp://$address"); + } elseif (microtime(true) - $time_start > $timeout) { + break; + } + continue; + } + $recv_len = strlen($all_buffer); + if (!$pack_len && $recv_len >= 4) { + $pack_len= current(unpack('N', $all_buffer)); + } + // 回复的数据都是以\n结尾 + if (($pack_len && $recv_len >= $pack_len + 4) || microtime(true) - $time_start > $timeout) { + break; + } + } + // 返回结果 + return unserialize(substr($all_buffer, 4)); + } else { + throw new Exception("sendAndRecv($address, \$bufer) fail ! Can not send data!", 502); + } + } + + /** + * 发送数据到网关 + * + * @param string $address + * @param array $gateway_data + * @return bool + */ + protected static function sendToGateway($address, $gateway_data) + { + return static::sendBufferToGateway($address, GatewayProtocol::encode($gateway_data)); + } + + /** + * 发送buffer数据到网关 + * @param string $address + * @param string $gateway_buffer + * @return bool + */ + protected static function sendBufferToGateway($address, $gateway_buffer) + { + // 有$businessWorker说明是workerman环境,使用$businessWorker发送数据 + if (static::$businessWorker) { + if (!isset(static::$businessWorker->gatewayConnections[$address])) { + return false; + } + return static::$businessWorker->gatewayConnections[$address]->send($gateway_buffer, true); + } + // 非workerman环境 + $gateway_buffer = static::$secretKey ? static::generateAuthBuffer() . $gateway_buffer : $gateway_buffer; + $flag = static::$persistentConnection ? STREAM_CLIENT_PERSISTENT | STREAM_CLIENT_CONNECT : STREAM_CLIENT_CONNECT; + $client = stream_socket_client("tcp://$address", $errno, $errmsg, static::$connectTimeout, $flag); + return strlen($gateway_buffer) == stream_socket_sendto($client, $gateway_buffer); + } + + /** + * 向所有 gateway 发送数据 + * + * @param string $gateway_data + * @throws Exception + * + * @return void + */ + protected static function sendToAllGateway($gateway_data) + { + $buffer = GatewayProtocol::encode($gateway_data); + // 如果有businessWorker实例,说明运行在workerman环境中,通过businessWorker中的长连接发送数据 + if (static::$businessWorker) { + foreach (static::$businessWorker->gatewayConnections as $gateway_connection) { + /** @var TcpConnection $gateway_connection */ + $gateway_connection->send($buffer, true); + } + } // 运行在其它环境中,通过注册中心得到gateway地址 + else { + $all_addresses = static::getAllGatewayAddressesFromRegister(); + foreach ($all_addresses as $address) { + static::sendBufferToGateway($address, $buffer); + } + } + } + + /** + * 踢掉某个网关的 socket + * + * @param string $address + * @param int $connection_id + * @return bool + */ + protected static function kickAddress($address, $connection_id, $message) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_KICK; + $gateway_data['connection_id'] = $connection_id; + $gateway_data['body'] = $message; + return static::sendToGateway($address, $gateway_data); + } + + /** + * 销毁某个网关的 socket + * + * @param string $address + * @param int $connection_id + * @return bool + */ + protected static function destroyAddress($address, $connection_id) + { + $gateway_data = GatewayProtocol::$empty; + $gateway_data['cmd'] = GatewayProtocol::CMD_DESTROY; + $gateway_data['connection_id'] = $connection_id; + return static::sendToGateway($address, $gateway_data); + } + + /** + * 将clientid数组转换成address数组 + * + * @param array $client_id_array + * @return array + */ + protected static function clientIdArrayToAddressArray(array $client_id_array) + { + $address_connection_array = array(); + foreach ($client_id_array as $client_id) { + $address_data = Context::clientIdToAddress($client_id); + if ($address_data) { + $address = long2ip($address_data['local_ip']) . + ":{$address_data['local_port']}"; + $address_connection_array[$address][$address_data['connection_id']] = $address_data['connection_id']; + } + } + return $address_connection_array; + } + + /** + * 设置 gateway 实例 + * + * @param \GatewayWorker\BusinessWorker $business_worker_instance + */ + public static function setBusinessWorker($business_worker_instance) + { + static::$businessWorker = $business_worker_instance; + } + + /** + * 获取通过注册中心获取所有 gateway 通讯地址 + * + * @return array + * @throws Exception + */ + protected static function getAllGatewayAddressesFromRegister() + { + static $addresses_cache, $last_update; + $time_now = time(); + $expiration_time = 1; + $register_addresses = (array)static::$registerAddress; + if(empty($addresses_cache) || $time_now - $last_update > $expiration_time) { + foreach ($register_addresses as $register_address) { + $client = stream_socket_client('tcp://' . $register_address, $errno, $errmsg, static::$connectTimeout); + if (!$client) { + continue; + } + } + if (!$client) { + throw new Exception('Can not connect to tcp://' . $register_address . ' ' . $errmsg); + } + + fwrite($client, '{"event":"worker_connect","secret_key":"' . static::$secretKey . '"}' . "\n"); + stream_set_timeout($client, 5); + $ret = fgets($client, 655350); + if (!$ret || !$data = json_decode(trim($ret), true)) { + throw new Exception('getAllGatewayAddressesFromRegister fail. tcp://' . + $register_address . ' return ' . var_export($ret, true)); + } + $last_update = $time_now; + $addresses_cache = $data['addresses']; + } + if (!$addresses_cache) { + throw new Exception('Gateway::getAllGatewayAddressesFromRegister() with registerAddress:' . + json_encode(static::$registerAddress) . ' return ' . var_export($addresses_cache, true)); + } + return $addresses_cache; + } + + /** + * 检查群组id是否合法 + * + * @param $group + * @return bool + */ + protected static function isValidGroupId($group) + { + if (empty($group)) { + echo new \Exception('group('.var_export($group, true).') empty'); + return false; + } + return true; + } +} + +if (!class_exists('\Protocols\GatewayProtocol')) { + class_alias('GatewayWorker\Protocols\GatewayProtocol', 'Protocols\GatewayProtocol'); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php new file mode 100644 index 0000000..9eec3be --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Protocols/GatewayProtocol.php @@ -0,0 +1,216 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker\Protocols; + +/** + * Gateway 与 Worker 间通讯的二进制协议 + * + * struct GatewayProtocol + * { + * unsigned int pack_len, + * unsigned char cmd,//命令字 + * unsigned int local_ip, + * unsigned short local_port, + * unsigned int client_ip, + * unsigned short client_port, + * unsigned int connection_id, + * unsigned char flag, + * unsigned short gateway_port, + * unsigned int ext_len, + * char[ext_len] ext_data, + * char[pack_length-HEAD_LEN] body//包体 + * } + * NCNnNnNCnN + */ +class GatewayProtocol +{ + // 发给worker,gateway有一个新的连接 + const CMD_ON_CONNECT = 1; + + // 发给worker的,客户端有消息 + const CMD_ON_MESSAGE = 3; + + // 发给worker上的关闭链接事件 + const CMD_ON_CLOSE = 4; + + // 发给gateway的向单个用户发送数据 + const CMD_SEND_TO_ONE = 5; + + // 发给gateway的向所有用户发送数据 + const CMD_SEND_TO_ALL = 6; + + // 发给gateway的踢出用户 + // 1、如果有待发消息,将在发送完后立即销毁用户连接 + // 2、如果无待发消息,将立即销毁用户连接 + const CMD_KICK = 7; + + // 发给gateway的立即销毁用户连接 + const CMD_DESTROY = 8; + + // 发给gateway,通知用户session更新 + const CMD_UPDATE_SESSION = 9; + + // 获取在线状态 + const CMD_GET_ALL_CLIENT_SESSIONS = 10; + + // 判断是否在线 + const CMD_IS_ONLINE = 11; + + // client_id绑定到uid + const CMD_BIND_UID = 12; + + // 解绑 + const CMD_UNBIND_UID = 13; + + // 向uid发送数据 + const CMD_SEND_TO_UID = 14; + + // 根据uid获取绑定的clientid + const CMD_GET_CLIENT_ID_BY_UID = 15; + + // 加入组 + const CMD_JOIN_GROUP = 20; + + // 离开组 + const CMD_LEAVE_GROUP = 21; + + // 向组成员发消息 + const CMD_SEND_TO_GROUP = 22; + + // 获取组成员 + const CMD_GET_CLIENT_SESSIONS_BY_GROUP = 23; + + // 获取组在线连接数 + const CMD_GET_CLIENT_COUNT_BY_GROUP = 24; + + // 按照条件查找 + const CMD_SELECT = 25; + + // 获取在线的群组ID + const CMD_GET_GROUP_ID_LIST = 26; + + // 取消分组 + const CMD_UNGROUP = 27; + + // worker连接gateway事件 + const CMD_WORKER_CONNECT = 200; + + // 心跳 + const CMD_PING = 201; + + // GatewayClient连接gateway事件 + const CMD_GATEWAY_CLIENT_CONNECT = 202; + + // 根据client_id获取session + const CMD_GET_SESSION_BY_CLIENT_ID = 203; + + // 发给gateway,覆盖session + const CMD_SET_SESSION = 204; + + // 当websocket握手时触发,只有websocket协议支持此命令字 + const CMD_ON_WEBSOCKET_CONNECT = 205; + + // 包体是标量 + const FLAG_BODY_IS_SCALAR = 0x01; + + // 通知gateway在send时不调用协议encode方法,在广播组播时提升性能 + const FLAG_NOT_CALL_ENCODE = 0x02; + + /** + * 包头长度 + * + * @var int + */ + const HEAD_LEN = 28; + + public static $empty = array( + 'cmd' => 0, + 'local_ip' => 0, + 'local_port' => 0, + 'client_ip' => 0, + 'client_port' => 0, + 'connection_id' => 0, + 'flag' => 0, + 'gateway_port' => 0, + 'ext_data' => '', + 'body' => '', + ); + + /** + * 返回包长度 + * + * @param string $buffer + * @return int return current package length + */ + public static function input($buffer) + { + if (strlen($buffer) < self::HEAD_LEN) { + return 0; + } + + $data = unpack("Npack_len", $buffer); + return $data['pack_len']; + } + + /** + * 获取整个包的 buffer + * + * @param mixed $data + * @return string + */ + public static function encode($data) + { + $flag = (int)is_scalar($data['body']); + if (!$flag) { + $data['body'] = serialize($data['body']); + } + $data['flag'] |= $flag; + $ext_len = strlen($data['ext_data']); + $package_len = self::HEAD_LEN + $ext_len + strlen($data['body']); + return pack("NCNnNnNCnN", $package_len, + $data['cmd'], $data['local_ip'], + $data['local_port'], $data['client_ip'], + $data['client_port'], $data['connection_id'], + $data['flag'], $data['gateway_port'], + $ext_len) . $data['ext_data'] . $data['body']; + } + + /** + * 从二进制数据转换为数组 + * + * @param string $buffer + * @return array + */ + public static function decode($buffer) + { + $data = unpack("Npack_len/Ccmd/Nlocal_ip/nlocal_port/Nclient_ip/nclient_port/Nconnection_id/Cflag/ngateway_port/Next_len", + $buffer); + if ($data['ext_len'] > 0) { + $data['ext_data'] = substr($buffer, self::HEAD_LEN, $data['ext_len']); + if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) { + $data['body'] = substr($buffer, self::HEAD_LEN + $data['ext_len']); + } else { + $data['body'] = unserialize(substr($buffer, self::HEAD_LEN + $data['ext_len'])); + } + } else { + $data['ext_data'] = ''; + if ($data['flag'] & self::FLAG_BODY_IS_SCALAR) { + $data['body'] = substr($buffer, self::HEAD_LEN); + } else { + $data['body'] = unserialize(substr($buffer, self::HEAD_LEN)); + } + } + return $data; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Register.php b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Register.php new file mode 100644 index 0000000..2d3e1a4 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/gateway-worker/src/Register.php @@ -0,0 +1,190 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace GatewayWorker; + +use Workerman\Worker; +use Workerman\Lib\Timer; + +/** + * + * 注册中心,用于注册 Gateway 和 BusinessWorker + * + * @author walkor + * + */ +class Register extends Worker +{ + /** + * {@inheritdoc} + */ + public $name = 'Register'; + + /** + * {@inheritdoc} + */ + public $reloadable = false; + + /** + * 秘钥 + * @var string + */ + public $secretKey = ''; + + /** + * 所有 gateway 的连接 + * + * @var array + */ + protected $_gatewayConnections = array(); + + /** + * 所有 worker 的连接 + * + * @var array + */ + protected $_workerConnections = array(); + + /** + * 进程启动时间 + * + * @var int + */ + protected $_startTime = 0; + + /** + * {@inheritdoc} + */ + public function run() + { + // 设置 onMessage 连接回调 + $this->onConnect = array($this, 'onConnect'); + + // 设置 onMessage 回调 + $this->onMessage = array($this, 'onMessage'); + + // 设置 onClose 回调 + $this->onClose = array($this, 'onClose'); + + // 记录进程启动的时间 + $this->_startTime = time(); + + // 强制使用text协议 + $this->protocol = '\Workerman\Protocols\Text'; + + // 运行父方法 + parent::run(); + } + + /** + * 设置个定时器,将未及时发送验证的连接关闭 + * + * @param \Workerman\Connection\ConnectionInterface $connection + * @return void + */ + public function onConnect($connection) + { + $connection->timeout_timerid = Timer::add(10, function () use ($connection) { + Worker::log("Register auth timeout (".$connection->getRemoteIp()."). See http://wiki.workerman.net/Error4 for detail"); + $connection->close(); + }, null, false); + } + + /** + * 设置消息回调 + * + * @param \Workerman\Connection\ConnectionInterface $connection + * @param string $buffer + * @return void + */ + public function onMessage($connection, $buffer) + { + // 删除定时器 + Timer::del($connection->timeout_timerid); + $data = @json_decode($buffer, true); + if (empty($data['event'])) { + $error = "Bad request for Register service. Request info(IP:".$connection->getRemoteIp().", Request Buffer:$buffer). See http://wiki.workerman.net/Error4 for detail"; + Worker::log($error); + return $connection->close($error); + } + $event = $data['event']; + $secret_key = isset($data['secret_key']) ? $data['secret_key'] : ''; + // 开始验证 + switch ($event) { + // 是 gateway 连接 + case 'gateway_connect': + if (empty($data['address'])) { + echo "address not found\n"; + return $connection->close(); + } + if ($secret_key !== $this->secretKey) { + Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $this->_gatewayConnections[$connection->id] = $data['address']; + $this->broadcastAddresses(); + break; + // 是 worker 连接 + case 'worker_connect': + if ($secret_key !== $this->secretKey) { + Worker::log("Register: Key does not match ".var_export($secret_key, true)." !== ".var_export($this->secretKey, true)); + return $connection->close(); + } + $this->_workerConnections[$connection->id] = $connection; + $this->broadcastAddresses($connection); + break; + case 'ping': + break; + default: + Worker::log("Register unknown event:$event IP: ".$connection->getRemoteIp()." Buffer:$buffer. See http://wiki.workerman.net/Error4 for detail"); + $connection->close(); + } + } + + /** + * 连接关闭时 + * + * @param \Workerman\Connection\ConnectionInterface $connection + */ + public function onClose($connection) + { + if (isset($this->_gatewayConnections[$connection->id])) { + unset($this->_gatewayConnections[$connection->id]); + $this->broadcastAddresses(); + } + if (isset($this->_workerConnections[$connection->id])) { + unset($this->_workerConnections[$connection->id]); + } + } + + /** + * 向 BusinessWorker 广播 gateway 内部通讯地址 + * + * @param \Workerman\Connection\ConnectionInterface $connection + */ + public function broadcastAddresses($connection = null) + { + $data = array( + 'event' => 'broadcast_addresses', + 'addresses' => array_unique(array_values($this->_gatewayConnections)), + ); + $buffer = json_encode($data); + if ($connection) { + $connection->send($buffer); + return; + } + foreach ($this->_workerConnections as $con) { + $con->send($buffer); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/.gitignore b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/.gitignore new file mode 100644 index 0000000..5d71c07 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/.gitignore @@ -0,0 +1,3 @@ +.project +.buildpath +.settings/org.eclipse.php.core.prefs \ No newline at end of file diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Autoloader.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Autoloader.php new file mode 100644 index 0000000..a9d2064 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Autoloader.php @@ -0,0 +1,80 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +// 包含常量定义文件 +require_once __DIR__.'/Lib/Constants.php'; + +/** + * 自动加载类 + * @author walkor + */ +class Autoloader +{ + // 应用的初始化目录,作为加载类文件的参考目录 + protected static $_appInitPath = ''; + + /** + * 设置应用初始化目录 + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_appInitPath = $root_path; + } + + /** + * 根据命名空间加载文件 + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + // 相对路径 + $class_path = str_replace('\\', DIRECTORY_SEPARATOR ,$name); + // 如果是Workerman命名空间,则在当前目录寻找类文件 + if(strpos($name, 'Workerman\\') === 0) + { + $class_file = __DIR__.substr($class_path, strlen('Workerman')).'.php'; + } + else + { + // 先尝试在应用目录寻找文件 + if(self::$_appInitPath) + { + $class_file = self::$_appInitPath . DIRECTORY_SEPARATOR . $class_path.'.php'; + } + // 文件不存在,则在上一层目录寻找 + if(empty($class_file) || !is_file($class_file)) + { + $class_file = __DIR__.DIRECTORY_SEPARATOR.'..'.DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + // 找到文件 + if(is_file($class_file)) + { + // 加载 + require_once($class_file); + if(class_exists($name, false)) + { + return true; + } + } + return false; + } +} +// 设置类自动加载回调函数 +spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/AsyncTcpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/AsyncTcpConnection.php new file mode 100644 index 0000000..d1eaf44 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/AsyncTcpConnection.php @@ -0,0 +1,328 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Connect start time. + * + * @var string + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var resource + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + $address_info = parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2); + if (!$this->_remoteAddress) { + echo new \Exception('bad remote_address'); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 80; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; + $this->_remoteHost = $address_info['host']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + } + + $this->id = $this->_id = self::$_idRecorder++; + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + self::$statistics['connection_count']++; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_contextOption = $context_option; + static::$connections[$this->id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = microtime(true); + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = stream_context_create($this->_contextOption); + $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + STREAM_CLIENT_ASYNC_CONNECT); + } + // If failed attempt to emit onError callback. + if (!$this->_socket) { + $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reConnect($after = 0) { + $this->_status = self::STATUS_INITIAL; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection($socket) + { + // Remove EV_EXPECT for windows. + if(DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($socket, EventInterface::EV_EXCEPT); + } + // Check socket state. + if ($address = stream_socket_get_name($socket, true)) { + // Remove write listener. + Worker::$globalEvent->del($socket, EventInterface::EV_WRITE); + // Nonblocking. + stream_set_blocking($socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = socket_import_stream($socket); + socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); + socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); + } + // Register a listener waiting read event. + Worker::$globalEvent->add($socket, EventInterface::EV_READ, array($this, 'baseRead')); + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + $this->_sslHandshakeCompleted = true; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onConnect + if (method_exists($this->protocol, 'onConnect')) { + try { + call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } else { + // Connection failed. + $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/AsyncUdpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/AsyncUdpConnection.php new file mode 100644 index 0000000..f15e47d --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/AsyncUdpConnection.php @@ -0,0 +1,101 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = substr($address, 2); + $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}"); + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ConnectionInterface::$statistics['total_request']++; + try { + call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + return true; + } + + + /** + * Close connection. + * + * @param mixed $data + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + fclose($this->_socket); + return true; + } + +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/ConnectionInterface.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/ConnectionInterface.php new file mode 100644 index 0000000..f1a4f8f --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/ConnectionInterface.php @@ -0,0 +1,125 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/TcpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/TcpConnection.php new file mode 100644 index 0000000..6431138 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/TcpConnection.php @@ -0,0 +1,876 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callback + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callback + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Maximum acceptable packet size. + * + * @var int + */ + public static $maxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + self::$statistics['connection_count']++; + $this->id = $this->_id = self::$_idRecorder++; + $this->_socket = $socket; + stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer) { + if ($this->bufferIsFull()) { + self::$statistics['send_fail']++; + return false; + } + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return null; + } + + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + $len = @fwrite($this->_socket, $send_buffer, 8192); + // send successful. + if ($len === strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!is_resource($this->_socket) || feof($this->_socket)) { + self::$statistics['send_fail']++; + if ($this->onError) { + try { + call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return null; + } else { + if ($this->bufferIsFull()) { + self::$statistics['send_fail']++; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return ''; + } + return substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)substr(strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + $ret = stream_socket_enable_crypto($socket, true, STREAM_CRYPTO_METHOD_SSLv2_SERVER | + STREAM_CRYPTO_METHOD_SSLv3_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER); + // Negotiation has failed. + if(false === $ret) { + if (!feof($socket)) { + echo "\nSSL Handshake fail. \nBuffer:".bin2hex(fread($socket, 8182))."\n"; + } + return $this->destroy(); + } elseif(0 === $ret) { + // There isn't enough data and should try again. + return; + } + if (isset($this->onSslHandshake)) { + try { + call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + return; + } + + $buffer = @fread($socket, self::READ_BUFFER_SIZE); + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (feof($socket) || !is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= self::$maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + echo 'error package. package_length=' . var_export($this->_currentPackageLength, true); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + self::$statistics['total_request']++; + // The current packet length is equal to the length of the buffer. + if (strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + self::$statistics['total_request']++; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + $len = @fwrite($this->_socket, $this->_sendBuffer, 8192); + if ($len === strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = substr($this->_sendBuffer, $len); + } else { + self::$statistics['send_fail']++; + $this->destroy(); + } + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param TcpConnection $dest + * @return void + */ + public function pipe($dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->destroy(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } else { + if ($data !== null) { + $this->send($data, $raw); + } + $this->_status = self::STATUS_CLOSING; + } + if ($this->_sendBuffer === '') { + $this->destroy(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + return false; + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + // Close socket. + @fclose($this->_socket); + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onClose + if (method_exists($this->protocol, 'onClose')) { + try { + call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + self::$statistics['connection_count']--; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/UdpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/UdpConnection.php new file mode 100644 index 0000000..2e7aeee --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Connection/UdpConnection.php @@ -0,0 +1,191 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return trim(substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return ''; + } + return substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)substr(strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Ev.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Ev.php new file mode 100644 index 0000000..5018fec --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Ev.php @@ -0,0 +1,184 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param \EvWatcher $event + */ + public function timerCallback($event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_allEvents as $event) { + $event->stop(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Event.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Event.php new file mode 100644 index 0000000..1322c73 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Event.php @@ -0,0 +1,199 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + $this->_eventBase = new \EventBase(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = \Event::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \Event($this->_eventBase, -1, \Event::TIMEOUT|\Event::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Event::READ | \Event::PERSIST : \Event::WRITE | \Event::PERSIST; + $event = new \Event($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + $event->del(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/EventInterface.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/EventInterface.php new file mode 100644 index 0000000..eb4a9d5 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/EventInterface.php @@ -0,0 +1,100 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param mixed $args + * @return bool + */ + public function add($fd, $flag, $func, $args = null); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Libevent.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Libevent.php new file mode 100644 index 0000000..d5a8409 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Libevent.php @@ -0,0 +1,217 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = EV_SIGNAL | EV_PERSIST; + $this->_eventSignal[$fd_key] = event_new(); + if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = event_new(); + $timer_id = (int)$event; + if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; + + $event = event_new(); + + if (!event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + event_del($event); + } + } +} + diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/ExtEventLoop.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/ExtEventLoop.php new file mode 100644 index 0000000..a145256 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/ExtEventLoop.php @@ -0,0 +1,173 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends \React\EventLoop\ExtEventLoop +{ + /** + * Event base. + * + * @var EventBase + */ + protected $_eventBase = null; + + /** + * All signal Event instances. + * + * @var array + */ + protected $_signalEvents = array(); + + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * Add event listener to event loop. + * + * @param $fd + * @param $flag + * @param $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $timer_obj = $this->addTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + return $this->removeSignal($fd); + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE; + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + /** + * Construct + */ + public function __construct() + { + parent::__construct(); + $class = new \ReflectionClass('\React\EventLoop\ExtEventLoop'); + $property = $class->getProperty('eventBase'); + $property->setAccessible(true); + $this->_eventBase = $property->getValue($this); + } + + /** + * Add signal handler. + * + * @param $signal + * @param $callback + * @return bool + */ + public function addSignal($signal, $callback) + { + $event = \Event::signal($this->_eventBase, $signal, $callback); + if (!$event||!$event->add()) { + return false; + } + $this->_signalEvents[$signal] = $event; + } + + /** + * Remove signal handler. + * + * @param $signal + */ + public function removeSignal($signal) + { + if (isset($this->_signalEvents[$signal])) { + $this->_signalEvents[$signal]->del(); + unset($this->_signalEvents[$signal]); + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_signalEvents as $event) { + $event->del(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/LibEventLoop.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/LibEventLoop.php new file mode 100644 index 0000000..7a3a93a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/LibEventLoop.php @@ -0,0 +1,174 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class LibEventLoop + * @package Workerman\Events\React + */ +class LibEventLoop extends \React\EventLoop\LibEventLoop +{ + /** + * Event base. + * + * @var event_base resource + */ + protected $_eventBase = null; + + /** + * All signal Event instances. + * + * @var array + */ + protected $_signalEvents = array(); + + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * Add event listener to event loop. + * + * @param $fd + * @param $flag + * @param $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $timer_obj = $this->addTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + return $this->removeSignal($fd); + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE; + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + /** + * Construct. + */ + public function __construct() + { + parent::__construct(); + $class = new \ReflectionClass('\React\EventLoop\LibEventLoop'); + $property = $class->getProperty('eventBase'); + $property->setAccessible(true); + $this->_eventBase = $property->getValue($this); + } + + /** + * Add signal handler. + * + * @param $signal + * @param $callback + * @return bool + */ + public function addSignal($signal, $callback) + { + $event = event_new(); + $this->_signalEvents[$signal] = $event; + event_set($event, $signal, EV_SIGNAL | EV_PERSIST, $callback); + event_base_set($event, $this->_eventBase); + event_add($event); + } + + /** + * Remove signal handler. + * + * @param $signal + */ + public function removeSignal($signal) + { + if (isset($this->_signalEvents[$signal])) { + $event = $this->_signalEvents[$signal]; + event_del($event); + unset($this->_signalEvents[$signal]); + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_signalEvents as $event) { + event_del($event); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/StreamSelectLoop.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/StreamSelectLoop.php new file mode 100644 index 0000000..2b4fc5a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/React/StreamSelectLoop.php @@ -0,0 +1,176 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends \React\EventLoop\StreamSelectLoop +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * Add event listener to event loop. + * + * @param $fd + * @param $flag + * @param $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + return $this->removeSignal($fd); + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE; + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + /** + * Add signal handler. + * + * @param $signal + * @param $callback + * @return bool + */ + public function addSignal($signal, $callback) + { + if(DIRECTORY_SEPARATOR === '/') { + pcntl_signal($signal, $callback); + } + } + + /** + * Remove signal handler. + * + * @param $signal + */ + public function removeSignal($signal) + { + if(DIRECTORY_SEPARATOR === '/') { + pcntl_signal($signal, SIG_IGN); + } + } + + /** + * Emulate a stream_select() implementation that does not break when passed + * empty stream arrays. + * + * @param array &$read An array of read streams to select upon. + * @param array &$write An array of write streams to select upon. + * @param integer|null $timeout Activity timeout in microseconds, or null to wait forever. + * + * @return integer|false The total number of streams that are ready for read/write. + * Can return false if stream_select() is interrupted by a signal. + */ + protected function streamSelect(array &$read, array &$write, $timeout) + { + if ($read || $write) { + $except = null; + // Calls signal handlers for pending signals + if(DIRECTORY_SEPARATOR === '/') { + pcntl_signal_dispatch(); + } + // suppress warnings that occur, when stream_select is interrupted by a signal + return @stream_select($read, $write, $except, $timeout === null ? null : 0, $timeout); + } + + // Calls signal handlers for pending signals + if(DIRECTORY_SEPARATOR === '/') { + pcntl_signal_dispatch(); + } + $timeout && usleep($timeout); + + return 0; + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Select.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Select.php new file mode 100644 index 0000000..c769bbf --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Events/Select.php @@ -0,0 +1,322 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. + $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, + STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + if($this->channel) { + stream_set_blocking($this->channel[0], 0); + $this->_readFds[0] = $this->channel[0]; + } + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_readFds[$fd_key] = $fd; + break; + case self::EV_WRITE: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_writeFds[$fd_key] = $fd; + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - microtime(true)) * 1000000; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = microtime(true); + $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $this->_scheduler->insert($timer_id, -$next_run_time); + } + call_user_func_array($task_data[0], $task_data[1]); + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + continue; + } + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + $e = null; + while (1) { + if(DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_writeFds; + + // Waiting read/write/signal/timeout events. + $ret = @stream_select($read, $write, $except, 0, $this->_selectTimeout); + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Lib/Constants.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Lib/Constants.php new file mode 100644 index 0000000..2f9d3df --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Lib/Constants.php @@ -0,0 +1,40 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +// Date.timezone +if (!ini_get('date.timezone')) { + date_default_timezone_set('Asia/Shanghai'); +} +// Display errors. +ini_set('display_errors', 'on'); +// Reporting all. +error_reporting(E_ALL); + +// Reset opcache. +if (function_exists('opcache_reset')) { + opcache_reset(); +} + +// For onError callback. +define('WORKERMAN_CONNECT_FAIL', 1); +// For onError callback. +define('WORKERMAN_SEND_FAIL', 2); + +// Compatible with php7 +if(!class_exists('Error')) +{ + class Error extends Exception + { + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Lib/Timer.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Lib/Timer.php new file mode 100644 index 0000000..4bd4ac5 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Lib/Timer.php @@ -0,0 +1,176 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +use Workerman\Events\EventInterface; +use Exception; + +/** + * Timer. + * + * example: + * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var \Workerman\Events\EventInterface + */ + protected static $_event = null; + + /** + * Init. + * + * @param \Workerman\Events\EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + } else { + pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param int $time_interval + * @param callback $func + * @param mixed $args + * @param bool $persistent + * @return int/false + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + echo new Exception("bad time_interval"); + return false; + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + if (!is_callable($func)) { + echo new Exception("not callable"); + return false; + } + + if (empty(self::$_tasks)) { + pcntl_alarm(1); + } + + $time_now = time(); + $run_time = $time_now + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); + return 1; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + pcntl_alarm(0); + return; + } + + $time_now = time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + echo $e; + } + if ($persistent) { + self::add($time_interval, $task_func, $task_args); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + return false; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = array(); + pcntl_alarm(0); + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/MIT-LICENSE.txt b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Frame.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Frame.php new file mode 100644 index 0000000..4a6b13e --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (strlen($buffer) < 4) { + return 0; + } + $unpack_data = unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + strlen($buffer); + return pack('N', $total_length) . $buffer; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Http.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Http.php new file mode 100644 index 0000000..98add4a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Http.php @@ -0,0 +1,585 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * http protocol + */ +class Http +{ + /** + * The supported HTTP methods + * @var array + */ + public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'); + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + if (!strpos($recv_buffer, "\r\n\r\n")) { + // Judge whether the package length exceeds the limit. + if (strlen($recv_buffer) >= TcpConnection::$maxPackageSize) { + $connection->close(); + return 0; + } + return 0; + } + + list($header,) = explode("\r\n\r\n", $recv_buffer, 2); + $method = substr($header, 0, strpos($header, ' ')); + + if(in_array($method, static::$methods)) { + return static::getRequestSize($header, $method); + }else{ + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + } + + /** + * Get whole size of the request + * includes the request headers and request body. + * @param string $header The request headers + * @param string $method The request method + * @return integer + */ + protected static function getRequestSize($header, $method) + { + if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') { + return strlen($header) + 4; + } + $match = array(); + if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { + $content_length = isset($match[1]) ? $match[1] : 0; + return $content_length + strlen($header) + 4; + } + return 0; + } + + /** + * Parse $_POST、$_GET、$_COOKIE. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return array + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + // Init. + $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); + $GLOBALS['HTTP_RAW_POST_DATA'] = ''; + // Clear cache. + HttpCache::$header = array('Connection' => 'Connection: keep-alive'); + HttpCache::$instance = new HttpCache(); + // $_SERVER + $_SERVER = array( + 'QUERY_STRING' => '', + 'REQUEST_METHOD' => '', + 'REQUEST_URI' => '', + 'SERVER_PROTOCOL' => '', + 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION, + 'SERVER_NAME' => '', + 'HTTP_HOST' => '', + 'HTTP_USER_AGENT' => '', + 'HTTP_ACCEPT' => '', + 'HTTP_ACCEPT_LANGUAGE' => '', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_COOKIE' => '', + 'HTTP_CONNECTION' => '', + 'REMOTE_ADDR' => '', + 'REMOTE_PORT' => '0', + 'REQUEST_TIME' => time() + ); + + // Parse headers. + list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); + $header_data = explode("\r\n", $http_header); + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + $http_post_boundary = ''; + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + // content-type + case 'CONTENT_TYPE': + if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { + if ($pos = strpos($value, ';')) { + $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos); + } else { + $_SERVER['CONTENT_TYPE'] = $value; + } + } else { + $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; + $http_post_boundary = '--' . $match[1]; + } + break; + case 'CONTENT_LENGTH': + $_SERVER['CONTENT_LENGTH'] = $value; + break; + } + } + + // Parse $_POST. + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_SERVER['CONTENT_TYPE'])) { + switch ($_SERVER['CONTENT_TYPE']) { + case 'multipart/form-data': + self::parseUploadFiles($http_body, $http_post_boundary); + break; + case 'application/x-www-form-urlencoded': + parse_str($http_body, $_POST); + break; + } + } + } + + // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA + $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + + // REQUEST + $_REQUEST = array_merge($_GET, $_POST); + + // REMOTE_ADDR REMOTE_PORT + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + + return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); + } + + /** + * Http encode. + * + * @param string $content + * @param TcpConnection $connection + * @return string + */ + public static function encode($content, TcpConnection $connection) + { + // Default http-code. + if (!isset(HttpCache::$header['Http-Code'])) { + $header = "HTTP/1.1 200 OK\r\n"; + } else { + $header = HttpCache::$header['Http-Code'] . "\r\n"; + unset(HttpCache::$header['Http-Code']); + } + + // Content-Type + if (!isset(HttpCache::$header['Content-Type'])) { + $header .= "Content-Type: text/html;charset=utf-8\r\n"; + } + + // other headers + foreach (HttpCache::$header as $key => $item) { + if ('Set-Cookie' === $key && is_array($item)) { + foreach ($item as $it) { + $header .= $it . "\r\n"; + } + } else { + $header .= $item . "\r\n"; + } + } + + // header + $header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; + + // save session + self::sessionWriteClose(); + + // the whole http package + return $header . $content; + } + + /** + * 设置http头 + * + * @return bool|void + */ + public static function header($content, $replace = true, $http_response_code = 0) + { + if (PHP_SAPI != 'cli') { + return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); + } + if (strpos($content, 'HTTP') === 0) { + $key = 'Http-Code'; + } else { + $key = strstr($content, ":", true); + if (empty($key)) { + return false; + } + } + + if ('location' === strtolower($key) && !$http_response_code) { + return self::header($content, true, 302); + } + + if (isset(HttpCache::$codes[$http_response_code])) { + HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; + if ($key === 'Http-Code') { + return true; + } + } + + if ($key === 'Set-Cookie') { + HttpCache::$header[$key][] = $content; + } else { + HttpCache::$header[$key] = $content; + } + + return true; + } + + /** + * Remove header. + * + * @param string $name + * @return void + */ + public static function headerRemove($name) + { + if (PHP_SAPI != 'cli') { + header_remove($name); + return; + } + unset(HttpCache::$header[$name]); + } + + /** + * Set cookie. + * + * @param string $name + * @param string $value + * @param integer $maxage + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $HTTPOnly + * @return bool|void + */ + public static function setcookie( + $name, + $value = '', + $maxage = 0, + $path = '', + $domain = '', + $secure = false, + $HTTPOnly = false + ) { + if (PHP_SAPI != 'cli') { + return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); + } + return self::header( + 'Set-Cookie: ' . $name . '=' . rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . (empty($maxage) ? '' : '; Max-Age=' . $maxage) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$HTTPOnly ? '' : '; HttpOnly'), false); + } + + /** + * sessionStart + * + * @return bool + */ + public static function sessionStart() + { + if (PHP_SAPI != 'cli') { + return session_start(); + } + + self::tryGcSessions(); + + if (HttpCache::$instance->sessionStarted) { + echo "already sessionStarted\n"; + return true; + } + HttpCache::$instance->sessionStarted = true; + // Generate a SID. + if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName])) { + $file_name = tempnam(HttpCache::$sessionPath, 'ses'); + if (!$file_name) { + return false; + } + HttpCache::$instance->sessionFile = $file_name; + $session_id = substr(basename($file_name), strlen('ses')); + return self::setcookie( + HttpCache::$sessionName + , $session_id + , ini_get('session.cookie_lifetime') + , ini_get('session.cookie_path') + , ini_get('session.cookie_domain') + , ini_get('session.cookie_secure') + , ini_get('session.cookie_httponly') + ); + } + if (!HttpCache::$instance->sessionFile) { + HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses' . $_COOKIE[HttpCache::$sessionName]; + } + // Read session from session file. + if (HttpCache::$instance->sessionFile) { + $raw = file_get_contents(HttpCache::$instance->sessionFile); + if ($raw) { + $_SESSION = unserialize($raw); + } + } + return true; + } + + /** + * Save session. + * + * @return bool + */ + public static function sessionWriteClose() + { + if (PHP_SAPI != 'cli') { + return session_write_close(); + } + if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { + $session_str = serialize($_SESSION); + if ($session_str && HttpCache::$instance->sessionFile) { + return file_put_contents(HttpCache::$instance->sessionFile, $session_str); + } + } + return empty($_SESSION); + } + + /** + * End, like call exit in php-fpm. + * + * @param string $msg + * @throws \Exception + */ + public static function end($msg = '') + { + if (PHP_SAPI != 'cli') { + exit($msg); + } + if ($msg) { + echo $msg; + } + throw new \Exception('jump_exit'); + } + + /** + * Get mime types. + * + * @return string + */ + public static function getMimeTypesFile() + { + return __DIR__ . '/Http/mime.types'; + } + + /** + * Parse $_FILES. + * + * @param string $http_body + * @param string $http_post_boundary + * @return void + */ + protected static function parseUploadFiles($http_body, $http_post_boundary) + { + $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); + $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); + if ($boundary_data_array[0] === '') { + unset($boundary_data_array[0]); + } + $key = -1; + foreach ($boundary_data_array as $boundary_data_buffer) { + list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); + // Remove \r\n from the end of buffer. + $boundary_value = substr($boundary_value, 0, -2); + $key ++; + foreach (explode("\r\n", $boundary_header_buffer) as $item) { + list($header_key, $header_value) = explode(": ", $item); + $header_key = strtolower($header_key); + switch ($header_key) { + case "content-disposition": + // Is file data. + if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { + // Parse $_FILES. + $_FILES[$key] = array( + 'name' => $match[1], + 'file_name' => $match[2], + 'file_data' => $boundary_value, + 'file_size' => strlen($boundary_value), + ); + continue; + } // Is post field. + else { + // Parse $_POST. + if (preg_match('/name="(.*?)"$/', $header_value, $match)) { + $_POST[$match[1]] = $boundary_value; + } + } + break; + case "content-type": + // add file_type + $_FILES[$key]['file_type'] = trim($header_value); + break; + } + } + } + } + + /** + * Try GC sessions. + * + * @return void + */ + public static function tryGcSessions() + { + if (HttpCache::$sessionGcProbability <= 0 || + HttpCache::$sessionGcDivisor <= 0 || + rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { + return; + } + + $time_now = time(); + foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { + if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { + unlink($file); + } + } + } +} + +/** + * Http cache for the current http response. + */ +class HttpCache +{ + public static $codes = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + + /** + * @var HttpCache + */ + public static $instance = null; + public static $header = array(); + public static $sessionPath = ''; + public static $sessionName = ''; + public static $sessionGcProbability = 1; + public static $sessionGcDivisor = 1000; + public static $sessionGcMaxLifeTime = 1440; + public $sessionStarted = false; + public $sessionFile = ''; + + public static function init() + { + self::$sessionName = ini_get('session.name'); + self::$sessionPath = @session_save_path(); + if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) { + self::$sessionPath = sys_get_temp_dir(); + } + + if ($gc_probability = ini_get('session.gc_probability')) { + self::$sessionGcProbability = $gc_probability; + } + + if ($gc_divisor = ini_get('session.gc_divisor')) { + self::$sessionGcDivisor = $gc_divisor; + } + + if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) { + self::$sessionGcMaxLifeTime = $gc_max_life_time; + } + } +} + +HttpCache::init(); diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Http/mime.types b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Http/mime.types new file mode 100644 index 0000000..8a218b2 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Http/mime.types @@ -0,0 +1,80 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/ProtocolInterface.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/ProtocolInterface.php new file mode 100644 index 0000000..9afe984 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param ConnectionInterface $connection + * @param string $recv_buffer + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param ConnectionInterface $connection + * @param string $recv_buffer + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param ConnectionInterface $connection + * @param mixed $data + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Text.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Text.php new file mode 100644 index 0000000..189baf4 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + // Judge whether the package length exceeds the limit. + if (strlen($buffer) >= TcpConnection::$maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return trim($buffer); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Websocket.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Websocket.php new file mode 100644 index 0000000..2ab5635 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Websocket.php @@ -0,0 +1,473 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = strlen($buffer); + // We need more data. + if ($recv_len < 2) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $firstbyte = ord($buffer[0]); + $secondbyte = ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + $opcode = $firstbyte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Ping package. + case 0x9: + // Try to emit onWebSocketPing callback. + if (isset($connection->onWebSocketPing)) { + try { + call_user_func($connection->onWebSocketPing, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Send pong package to client. + else { + $connection->send(pack('H*', '8a00'), true); + } + + // Consume data from receive buffer. + if (!$data_len) { + $head_len = $masked ? 6 : 2; + $connection->consumeRecvBuffer($head_len); + if ($recv_len > $head_len) { + return static::input(substr($buffer, $head_len), $connection); + } + return 0; + } + break; + // Pong package. + case 0xa: + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + call_user_func($connection->onWebSocketPong, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Consume data from receive buffer. + if (!$data_len) { + $head_len = $masked ? 6 : 2; + $connection->consumeRecvBuffer($head_len); + if ($recv_len > $head_len) { + return static::input(substr($buffer, $head_len), $connection); + } + return 0; + } + break; + // Wrong opcode. + default : + echo "error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"; + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $data_len = $arr['c1']*4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > TcpConnection::$maxPackageSize) { + echo "error package. package_length=$total_package_size\n"; + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); + } + $len = strlen($buffer); + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $first_byte = $connection->websocketType; + + if ($len <= 125) { + $encode_buffer = $first_byte . chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->websocketHandshake)) { + if (empty($connection->tmpWebsocketData)) { + $connection->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $masks = $data = $decoded = null; + $len = ord($buffer[1]) & 127; + if ($len === 126) { + $masks = substr($buffer, 4, 4); + $data = substr($buffer, 8); + } else { + if ($len === 127) { + $masks = substr($buffer, 10, 4); + $data = substr($buffer, 14); + } else { + $masks = substr($buffer, 2, 4); + $data = substr($buffer, 6); + } + } + for ($index = 0; $index < strlen($data); $index++) { + $decoded .= $data[$index] ^ $masks[$index % 4]; + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded = $connection->websocketDataBuffer . $decoded; + $connection->websocketDataBuffer = ''; + } + return $decoded; + } + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + protected static function dealHandshake($buffer, $connection) + { + // HTTP protocol. + if (0 === strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $heder_end_pos = strpos($buffer, "\r\n\r\n"); + if (!$heder_end_pos) { + return 0; + } + $header_length = $heder_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found.
This is a WebSocket service and can not be accessed via HTTP.
See http://wiki.workerman.net/Error1 for detail.", + true); + $connection->close(); + return 0; + } + // Calculation websocket key. + $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; + $handshake_message .= "Upgrade: websocket\r\n"; + $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; + $handshake_message .= "Connection: Upgrade\r\n"; + $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; + $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n\r\n"; + // Mark handshake complete.. + $connection->websocketHandshake = true; + // Websocket data buffer. + $connection->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + // Send handshake response. + $connection->send($handshake_message, true); + + // There are data waiting to be sent. + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + static::parseHttpHeader($buffer); + try { + call_user_func($connection->onWebSocketConnect, $connection, $buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + } + if (strlen($buffer) > $header_length) { + return static::input(substr($buffer, $header_length), $connection); + } + return 0; + } // Is flash policy-file-request. + elseif (0 === strpos($buffer, 'send($policy_xml, true); + $connection->consumeRecvBuffer(strlen($buffer)); + return 0; + } + // Bad websocket handshake request. + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket.
See http://wiki.workerman.net/Error1 for detail.", + true); + $connection->close(); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = explode("\r\n\r\n", $buffer, 2); + $header_data = explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Ws.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Ws.php new file mode 100644 index 0000000..a3a0193 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Protocols/Ws.php @@ -0,0 +1,433 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\TcpConnection; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, $connection) + { + if (empty($connection->handshakeStep)) { + echo "recv data before handshake. Buffer:" . bin2hex($buffer) . "\n"; + return false; + } + // Recv handshake response + if ($connection->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recv_len = strlen($buffer); + if ($recv_len < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = ord($buffer[0]); + $secondbyte = ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Ping package. + case 0x9: + // Try to emit onWebSocketPing callback. + if (isset($connection->onWebSocketPing)) { + try { + call_user_func($connection->onWebSocketPing, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Send pong package to client. + else { + $connection->send(pack('H*', '8a00'), true); + } + // Consume data from receive buffer. + if (!$data_len) { + $head_len = $masked ? 6 : 2; + $connection->consumeRecvBuffer($head_len); + if ($recv_len > $head_len) { + return self::input(substr($buffer, $head_len), $connection); + } + return 0; + } + break; + // Pong package. + case 0xa: + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + call_user_func($connection->onWebSocketPong, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Consume data from receive buffer. + if (!$data_len) { + $head_len = $masked ? 6 : 2; + $connection->consumeRecvBuffer($head_len); + if ($recv_len > $head_len) { + return self::input(substr($buffer, $head_len), $connection); + } + return 0; + } + break; + // Wrong opcode. + default : + echo "error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"; + $connection->close(); + return 0; + } + // Calculate packet length. + if ($data_len === 126) { + if (strlen($buffer) < 6) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $current_frame_length = $pack['total_len'] + 4; + } else if ($data_len === 127) { + if (strlen($buffer) < 10) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; + } else { + $current_frame_length = $data_len + 2; + } + + $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > TcpConnection::$maxPackageSize) { + echo "error package. package_length=$total_package_size\n"; + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->handshakeStep)) { + self::sendHandshake($connection); + } + $mask = 1; + $mask_key = "\x00\x00\x00\x00"; + + $pack = ''; + $length = $length_flag = strlen($payload); + if (65535 < $length) { + $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); + $length_flag = 127; + } else if (125 < $length) { + $pack = pack('n*', $length); + $length_flag = 126; + } + + $head = ($mask << 7) | $length_flag; + $head = $connection->websocketType . chr($head) . $pack; + + $frame = $head . $mask_key; + // append payload to frame: + for ($i = 0; $i < $length; $i++) { + $frame .= $payload[$i] ^ $mask_key[$i % 4]; + } + if ($connection->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, $connection) + { + $masked = ord($bytes[1]) >> 7; + $data_length = $masked ? ord($bytes[1]) & 127 : ord($bytes[1]); + $decoded_data = ''; + if ($masked === true) { + if ($data_length === 126) { + $mask = substr($bytes, 4, 4); + $coded_data = substr($bytes, 8); + } else if ($data_length === 127) { + $mask = substr($bytes, 10, 4); + $coded_data = substr($bytes, 14); + } else { + $mask = substr($bytes, 2, 4); + $coded_data = substr($bytes, 6); + } + for ($i = 0; $i < strlen($coded_data); $i++) { + $decoded_data .= $coded_data[$i] ^ $mask[$i % 4]; + } + } else { + if ($data_length === 126) { + $decoded_data = substr($bytes, 4); + } else if ($data_length === 127) { + $decoded_data = substr($bytes, 10); + } else { + $decoded_data = substr($bytes, 2); + } + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded_data; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded_data = $connection->websocketDataBuffer . $decoded_data; + $connection->websocketDataBuffer = ''; + } + return $decoded_data; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + self::sendHandshake($connection); + } + + /** + * Clean + * + * @param $connection + */ + public static function onClose($connection) + { + $connection->handshakeStep = null; + $connection->websocketCurrentFrameLength = 0; + $connection->tmpWebsocketData = ''; + $connection->websocketDataBuffer = ''; + if (!empty($connection->websocketPingTimer)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param \Workerman\Connection\TcpConnection $connection + * @return void + */ + public static function sendHandshake($connection) + { + if (!empty($connection->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". + "Host: $host\r\n". + "Connection: Upgrade\r\n". + "Upgrade: websocket\r\n". + "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". + "Sec-WebSocket-Version: 13\r\n". + "Sec-WebSocket-Key: " . base64_encode(md5(mt_rand(), true)) . "\r\n\r\n"; + $connection->send($header, true); + $connection->handshakeStep = 1; + $connection->websocketCurrentFrameLength = 0; + $connection->websocketDataBuffer = ''; + $connection->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + $pos = strpos($buffer, "\r\n\r\n"); + if ($pos) { + // handshake complete + $connection->handshakeStep = 2; + $handshake_response_length = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ + if (false === $connection->send(pack('H*', '8900'), true)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshake_response_length); + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (strlen($buffer) > $handshake_response_length) { + return self::input(substr($buffer, $handshake_response_length), $connection); + } + } + return 0; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/README.md b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/README.md new file mode 100644 index 0000000..3f80382 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/README.md @@ -0,0 +1,32 @@ +# workerman-for-win +workerman-for-win + +## 环境要求 +(php>=5.3.3) + +## 运行 +运行一个文件 +php your_file.php + +同时运行多个文件 +php your_file.php your_file2.php ... + +## 与Linux多进程版本的区别 +1、单进程,也就是说count属性无效 +2、由于php在win下无法fork进程,Applications/YourApp/start.php被拆成多个子启动项,如start_web.php start_gateway.php等,每个文件自动启动一个进程运行 +3、由于php在win下不支持信号,所以无法使用reload、status、restart、stop命令,也没有start命令 + +## 手册 +开发与Linux版本基本无差别,可以直接参考Linux版本手册 +http://doc3.workerman.net/ + +## 说明 +此版本可用于windows下开发使用,不建议用在生产环境 + +## 移植 +### windows到Linux(需要Linux的Workerman版本3.1.0及以上) +可以直接将Applications下的应用目录拷贝到Linux版本的Applications下直接运行 + +### Linux到windows +Linux下的应用需要将Applications/YourApp/start.php拆成多个启动项 + diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/WebServer.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/WebServer.php new file mode 100644 index 0000000..afa412a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/WebServer.php @@ -0,0 +1,301 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Protocols\Http; +use Workerman\Protocols\HttpCache; + +/** + * WebServer. + */ +class WebServer extends Worker +{ + /** + * Virtual host to path mapping. + * + * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] + */ + protected $serverRoot = array(); + + /** + * Mime mapping. + * + * @var array + */ + protected static $mimeTypeMap = array(); + + + /** + * Used to save user OnWorkerStart callback settings. + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * Add virtual host. + * + * @param string $domain + * @param string $root_path + * @return void + */ + public function addRoot($domain, $root_path) + { + $this->serverRoot[$domain] = $root_path; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, $context_option = array()) + { + list(, $address) = explode(':', $socket_name, 2); + parent::__construct('http:' . $address, $context_option); + $this->name = 'WebServer'; + } + + /** + * Run webserver instance. + * + * @see Workerman.Worker::run() + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onMessage = array($this, 'onMessage'); + parent::run(); + } + + /** + * Emit when process start. + * + * @throws \Exception + */ + public function onWorkerStart() + { + if (empty($this->serverRoot)) { + echo new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path'); + exit(250); + } + + // Init mimeMap. + $this->initMimeTypeMap(); + + // Try to emit onWorkerStart callback. + if ($this->_onWorkerStart) { + try { + call_user_func($this->_onWorkerStart, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + } + + /** + * Init mime map. + * + * @return void + */ + public function initMimeTypeMap() + { + $mime_file = Http::getMimeTypesFile(); + if (!is_file($mime_file)) { + $this->log("$mime_file mime.type file not fond"); + return; + } + $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($items)) { + $this->log("get $mime_file mime.type content fail"); + return; + } + foreach ($items as $content) { + if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $workerman_file_extension_var = $match[2]; + $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); + foreach ($workerman_file_extension_array as $workerman_file_extension) { + self::$mimeTypeMap[$workerman_file_extension] = $mime_type; + } + } + } + } + + /** + * Emit when http message coming. + * + * @param Connection\TcpConnection $connection + * @return void + */ + public function onMessage($connection) + { + // REQUEST_URI. + $workerman_url_info = parse_url($_SERVER['REQUEST_URI']); + if (!$workerman_url_info) { + Http::header('HTTP/1.1 400 Bad Request'); + $connection->close('

400 Bad Request

'); + return; + } + + $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; + + $workerman_path_info = pathinfo($workerman_path); + $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; + if ($workerman_file_extension === '') { + $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; + $workerman_file_extension = 'php'; + } + + $workerman_root_dir = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot); + + $workerman_file = "$workerman_root_dir/$workerman_path"; + + if ($workerman_file_extension === 'php' && !is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.php"; + if (!is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.html"; + $workerman_file_extension = 'html'; + } + } + + // File exsits. + if (is_file($workerman_file)) { + // Security check. + if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath, + $workerman_root_dir_realpath) + ) { + Http::header('HTTP/1.1 400 Bad Request'); + $connection->close('

400 Bad Request

'); + return; + } + + $workerman_file = realpath($workerman_file); + + // Request php file. + if ($workerman_file_extension === 'php') { + $workerman_cwd = getcwd(); + chdir($workerman_root_dir); + ini_set('display_errors', 'off'); + ob_start(); + // Try to include php file. + try { + // $_SERVER. + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + include $workerman_file; + } catch (\Exception $e) { + // Jump_exit? + if ($e->getMessage() != 'jump_exit') { + echo $e; + } + } + $content = ob_get_clean(); + ini_set('display_errors', 'on'); + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($content); + } else { + $connection->close($content); + } + chdir($workerman_cwd); + return; + } + + // Send file to client. + return self::sendFile($connection, $workerman_file); + } else { + // 404 + Http::header("HTTP/1.1 404 Not Found"); + $connection->close('404 File not found

404 Not Found

'); + return; + } + } + + public static function sendFile($connection, $file_path) + { + // Check 304. + $info = stat($file_path); + $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : ''; + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { + // Http 304. + if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { + // 304 + Http::header('HTTP/1.1 304 Not Modified'); + // Send nothing but http headers.. + $connection->close(''); + return; + } + } + + // Http header. + if ($modified_time) { + $modified_time = "Last-Modified: $modified_time\r\n"; + } + $file_size = filesize($file_path); + $file_info = pathinfo($file_path); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; + $header = "HTTP/1.1 200 OK\r\n"; + if (isset(self::$mimeTypeMap[$extension])) { + $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; + } else { + $header .= "Content-Type: application/octet-stream\r\n"; + $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; + } + $header .= "Connection: keep-alive\r\n"; + $header .= $modified_time; + $header .= "Content-Length: $file_size\r\n\r\n"; + $trunk_limit_size = 1024*1024; + if ($file_size < $trunk_limit_size) { + return $connection->send($header.file_get_contents($file_path), true); + } + $connection->send($header, true); + + // Read file content from disk piece by piece and send to client. + $connection->fileHandler = fopen($file_path, 'r'); + $do_write = function()use($connection) + { + // Send buffer not full. + while(empty($connection->bufferFull)) + { + // Read from disk. + $buffer = fread($connection->fileHandler, 8192); + // Read eof. + if($buffer === '' || $buffer === false) + { + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function($connection) + { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function($connection)use($do_write) + { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Worker.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Worker.php new file mode 100644 index 0000000..ea7139e --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/Worker.php @@ -0,0 +1,946 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +require_once __DIR__ . '/Lib/Constants.php'; + +use \Workerman\Events\Libevent; +use \Workerman\Events\Event; +use \Workerman\Events\React; +use \Workerman\Events\Select; +use \Workerman\Events\EventInterface; +use \Workerman\Connection\ConnectionInterface; +use \Workerman\Connection\TcpConnection; +use \Workerman\Connection\UdpConnection; +use \Workerman\Lib\Timer; +use \Workerman\Autoloader; +use \Exception; + +/** + * + * @author walkor + */ +class Worker +{ + /** + * 版本号 + * @var string + */ + const VERSION = '3.5.1'; + + /** + * 状态 启动中 + * @var int + */ + const STATUS_STARTING = 1; + + /** + * 状态 运行中 + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * 状态 停止 + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * 状态 平滑重启中 + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * 给子进程发送重启命令 KILL_WORKER_TIMER_TIME 秒后 + * 如果对应进程仍然未重启则强行杀死 + * @var int + */ + const KILL_WORKER_TIMER_TIME = 1; + + /** + * 默认的backlog,即内核中用于存放未被进程认领(accept)的连接队列长度 + * @var int + */ + const DEFAUL_BACKLOG = 1024; + + /** + * udp最大包长 + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * worker id + * @var int + */ + public $id = 0; + + /** + * worker的名称,用于在运行status命令时标记进程 + * @var string + */ + public $name = 'none'; + + /** + * 设置当前worker实例的进程数 + * @var int + */ + public $count = 1; + + /** + * 设置当前worker进程的运行用户,启动时需要root超级权限 + * @var string + */ + public $user = ''; + + /** + * 当前worker进程是否可以平滑重启 + * @var bool + */ + public $reloadable = true; + + /** + * reuse port + * @var bool + */ + public $reusePort = false; + + /** + * 当worker进程启动时,如果设置了$onWorkerStart回调函数,则运行 + * 此钩子函数一般用于进程启动后初始化工作 + * @var callback + */ + public $onWorkerStart = null; + + /** + * 当有客户端连接时,如果设置了$onConnect回调函数,则运行 + * @var callback + */ + public $onConnect = null; + + /** + * 当客户端连接上发来数据时,如果设置了$onMessage回调,则运行 + * @var callback + */ + public $onMessage = null; + + /** + * 当客户端的连接关闭时,如果设置了$onClose回调,则运行 + * @var callback + */ + public $onClose = null; + + /** + * 当客户端的连接发生错误时,如果设置了$onError回调,则运行 + * 错误一般为客户端断开连接导致数据发送失败、服务端的发送缓冲区满导致发送失败等 + * 具体错误码及错误详情会以参数的形式传递给回调,参见手册 + * @var callback + */ + public $onError = null; + + /** + * 当连接的发送缓冲区满时,如果设置了$onBufferFull回调,则执行 + * @var callback + */ + public $onBufferFull = null; + + /** + * 当链接的发送缓冲区被清空时,如果设置了$onBufferDrain回调,则执行 + * @var callback + */ + public $onBufferDrain = null; + + /** + * 当前进程退出时(由于平滑重启或者服务停止导致),如果设置了此回调,则运行 + * @var callback + */ + public $onWorkerStop = null; + + /** + * 当收到reload命令时的回调函数 + * @var callback + */ + public $onWorkerReload = null; + + /** + * 传输层协议 + * @var string + */ + public $transport = 'tcp'; + + /** + * 所有的客户端连接 + * @var array + */ + public $connections = array(); + + /** + * 应用层协议,由初始化worker时指定 + * 例如 new worker('http://0.0.0.0:8080');指定使用http协议 + * @var string + */ + protected $protocol = null; + + /** + * 当前worker实例初始化目录位置,用于设置应用自动加载的根目录 + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * 是否以守护进程的方式运行。运行start时加上-d参数会自动以守护进程方式运行 + * 例如 php start.php start -d + * @var bool + */ + public static $daemonize = false; + + /** + * 重定向标准输出,即将所有echo、var_dump等终端输出写到对应文件中 + * 注意 此参数只有在以守护进程方式运行时有效 + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * pid文件的路径及名称 + * 例如 Worker::$pidFile = '/tmp/workerman.pid'; + * 注意 此属性一般不必手动设置,默认会放到php临时目录中 + * @var string + */ + public static $pidFile = ''; + + /** + * 日志目录,默认在workerman根目录下,与Applications同级 + * 可以手动设置 + * 例如 Worker::$logFile = '/tmp/workerman.log'; + * @var unknown_type + */ + public static $logFile = ''; + + /** + * 全局事件轮询库,用于监听所有资源的可读可写事件 + * @var Select/Libevent + */ + public static $globalEvent = null; + + /** + * 主进程停止时触发的回调,Win系统下不起作用 + * @var unknown_type + */ + public static $onMasterStop = null; + + /** + * 事件轮询库类名 + * @var string + */ + public static $eventLoopClass = ''; + + /** + * 主进程pid + * @var int + */ + protected static $_masterPid = 0; + + /** + * 监听的socket + * @var stream + */ + protected $_mainSocket = null; + + /** + * socket名称,包括应用层协议+ip+端口号,在初始化worker时设置 + * 值类似 http://0.0.0.0:80 + * @var string + */ + protected $_socketName = ''; + + /** + * socket的上下文,具体选项设置可以在初始化worker时传递 + * @var context + */ + protected $_context = null; + + /** + * 所有的worker实例 + * @var array + */ + protected static $_workers = array(); + + /** + * 所有worker进程的pid + * 格式为 [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * @var array + */ + protected static $_pidMap = array(); + + /** + * 所有需要重启的进程pid + * 格式为 [pid=>pid, pid=>pid] + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * 当前worker状态 + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * 所有worke名称(name属性)中的最大长度,用于在运行 status 命令时格式化输出 + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * 所有socket名称(_socketName属性)中的最大长度,用于在运行 status 命令时格式化输出 + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * 所有user名称(user属性)中的最大长度,用于在运行 status 命令时格式化输出 + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * 运行 status 命令时用于保存结果的文件名 + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * 启动的全局入口文件 + * 例如 php start.php start ,则入口文件为start.php + * @var string + */ + protected static $_startFile = ''; + + /** + * 用来保存子进程句柄(windows) + * @var array + */ + protected static $_process = array(); + + /** + * 要执行的文件 + * @var array + */ + protected static $_startFiles = array(); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'libevent' => '\Workerman\Events\Libevent', + 'event' => '\Workerman\Events\Event' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * 运行所有worker实例 + * @return void + */ + public static function runAll() + { + // 初始化环境变量 + self::init(); + // 解析命令 + self::parseCommand(); + // 初始化所有worker实例,主要是监听端口 + self::initWorkers(); + // 展示启动界面 + self::displayUI(); + // 运行所有的worker + self::runAllWorkers(); + // 监控worker + self::monitorWorkers(); + } + + /** + * 初始化一些环境变量 + * @return void + */ + public static function init() + { + if(strpos(strtolower(PHP_OS), 'win') !== 0) + { + exit("workerman-for-win can not run in linux\n"); + } + if (false !== strpos(ini_get('disable_functions'), 'proc_open')) + { + exit("\r\nWarning: proc_open() has been disabled for security reasons. \r\n\r\nSee http://wiki.workerman.net/Error5\r\n"); + } + $backtrace = debug_backtrace(); + self::$_startFile = $backtrace[count($backtrace)-1]['file']; + // 没有设置日志文件,则生成一个默认值 + if(empty(self::$logFile)) + { + self::$logFile = __DIR__ . '/../workerman.log'; + } + // 标记状态为启动中 + self::$_status = self::STATUS_STARTING; + + $event_loop_class = self::getEventLoopName(); + self::$globalEvent = new $event_loop_class; + Timer::init(self::$globalEvent); + } + + /** + * 初始化所有的worker实例,主要工作为获得格式化所需数据及监听端口 + * @return void + */ + protected static function initWorkers() + { + foreach(self::$_workers as $worker) + { + // 没有设置worker名称,则使用none代替 + if(empty($worker->name)) + { + $worker->name = 'none'; + } + // 获得所有worker名称中最大长度 + $worker_name_length = strlen($worker->name); + if(self::$_maxWorkerNameLength < $worker_name_length) + { + self::$_maxWorkerNameLength = $worker_name_length; + } + // 获得所有_socketName中最大长度 + $socket_name_length = strlen($worker->getSocketName()); + if(self::$_maxSocketNameLength < $socket_name_length) + { + self::$_maxSocketNameLength = $socket_name_length; + } + $user_name_length = strlen($worker->user); + if(self::$_maxUserNameLength < $user_name_length) + { + self::$_maxUserNameLength = $user_name_length; + } + } + } + + /** + * 运行所有的worker + */ + public static function runAllWorkers() + { + // 只有一个start文件时执行run + if(count(self::$_startFiles) === 1) + { + // win不支持同一个页面执初始化多个worker + if(count(self::$_workers) > 1) + { + echo "@@@ Error: multi workers init in one php file are not support @@@\r\n"; + echo "@@@ Please visit http://wiki.workerman.net/Multi_woker_for_win @@@\r\n"; + } + elseif(count(self::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + // 执行worker的run方法 + reset(self::$_workers); + $worker = current(self::$_workers); + $worker->listen(); + // 子进程阻塞在这里 + $worker->run(); + exit("@@@child exit@@@\r\n"); + } + // 多个start文件则多进程打开 + elseif(count(self::$_startFiles) > 1) + { + self::$globalEvent = new Select(); + Timer::init(self::$globalEvent); + foreach(self::$_startFiles as $start_file) + { + self::openProcess($start_file); + } + } + // 没有start文件提示错误 + else + { + //exit("@@@no worker inited@@@\r\n"); + } + } + + /** + * 打开一个子进程 + * @param string $start_file + */ + public static function openProcess($start_file) + { + // 保存子进程的输出 + $start_file = realpath($start_file); + $std_file = sys_get_temp_dir() . '/'.str_replace(array('/', "\\", ':'), '_', $start_file).'.out.txt'; + // 将stdou stderr 重定向到文件 + $descriptorspec = array( + 0 => array('pipe', 'a'), // stdin + 1 => array('file', $std_file, 'w'), // stdout + 2 => array('file', $std_file, 'w') // stderr + ); + + // 保存stdin句柄,用来探测子进程是否关闭 + $pipes = array(); + + // 打开子进程 + $process= proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + + // 打开stdout stderr 文件句柄 + $std_handler = fopen($std_file, 'a+'); + // 非阻塞 + stream_set_blocking($std_handler, 0); + // 定时读取子进程的stdout stderr + $timer_id = Timer::add(0.1, function()use($std_handler) + { + echo fread($std_handler, 65535); + }); + + // 保存子进程句柄 + self::$_process[$start_file] = array($process, $start_file, $timer_id); + } + + /** + * 定时检查子进程是否退出了 + */ + protected static function monitorWorkers() + { + // 定时检查子进程是否退出了 + Timer::add(0.5, "\\Workerman\\Worker::checkWorkerStatus"); + + // 主进程loop + self::$globalEvent->loop(); + } + + public static function checkWorkerStatus() + { + foreach(self::$_process as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $timer_id = $process_data[2]; + $status = proc_get_status($process); + if(isset($status['running'])) + { + // 子进程退出了,重启一个子进程 + if(!$status['running']) + { + echo "process $start_file terminated and try to restart\n"; + Timer::del($timer_id); + @proc_close($process); + // 重新打开一个子进程 + self::openProcess($start_file); + } + } + else + { + echo "proc_get_status fail\n"; + } + } + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return self::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return self::$globalEvent; + } + + /** + * 展示启动界面 + * @return void + */ + protected static function displayUI() + { + global $argv; + // -q不打印 + if(in_array('-q', $argv)) + { + return; + } + echo "----------------------- WORKERMAN -----------------------------\n"; + echo 'Workerman version:' . Worker::VERSION . " PHP version:".PHP_VERSION."\n"; + echo "------------------------ WORKERS -------------------------------\n"; + echo "worker",str_pad('', self::$_maxWorkerNameLength+2-strlen('worker')), "listen",str_pad('', self::$_maxSocketNameLength+2-strlen('listen')), "processes ","status\n"; + foreach(self::$_workers as $worker) + { + echo str_pad($worker->name, self::$_maxWorkerNameLength+2),str_pad($worker->getSocketName(), self::$_maxSocketNameLength+2), str_pad(' '.$worker->count, 9), " [OK] \n";; + } + echo "----------------------------------------------------------------\n"; + echo "Press Ctrl-C to quit. Start success.\n"; + } + + /** + * 解析运行命令 + * php yourfile.php start | stop | restart | reload | status + * @return void + */ + public static function parseCommand() + { + global $argv; + foreach($argv as $file) + { + $ext = pathinfo($file, PATHINFO_EXTENSION ); + if($ext !== 'php') + { + continue; + } + if(is_file($file)) + { + self::$_startFiles[$file] = $file; + include_once $file; + } + } + } + + /** + * 执行关闭流程 + * @return void + */ + public static function stopAll() + { + self::$_status = self::STATUS_SHUTDOWN; + exit(0); + } + + /** + * 记录日志 + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg."\n"; + if(self::$_status === self::STATUS_STARTING || !self::$daemonize) + { + echo $msg; + } + file_put_contents(self::$logFile, date('Y-m-d H:i:s') . " " . $msg, FILE_APPEND | LOCK_EX); + } + + /** + * worker构造函数 + * @param string $socket_name + * @return void + */ + public function __construct($socket_name = '', $context_option = array()) + { + // 保存worker实例 + $this->workerId = spl_object_hash($this); + self::$_workers[$this->workerId] = $this; + self::$_pidMap[$this->workerId] = array(); + + // 获得实例化文件路径,用于自动加载设置根目录 + $backrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backrace[0]['file']); + + // 设置socket上下文 + if($socket_name) + { + $this->_socketName = $socket_name; + if(!isset($context_option['socket']['backlog'])) + { + $context_option['socket']['backlog'] = self::DEFAUL_BACKLOG; + } + $this->_context = stream_context_create($context_option); + } + + // 设置一个空的onMessage,当onMessage未设置时用来消费socket数据 + $this->onMessage = function(){}; + } + + /** + * 监听端口 + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName || $this->_mainSocket) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + // Get the application layer communication protocol and listening address. + list($scheme, $address) = explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + if(class_exists($scheme)){ + $this->protocol = $scheme; + } else { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + if (!isset(self::$_builtinTransports[$this->transport])) { + throw new \Exception('Bad worker->transport ' . var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + + $local_socket = self::$_builtinTransports[$this->transport] . ":" . $address; + + // Flag. + $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + stream_socket_enable_crypto($this->_mainSocket, false); + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && self::$_builtinTransports[$this->transport] === 'tcp') { + $socket = socket_import_stream($this->_mainSocket); + @socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); + @socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); + } + + // Non blocking. + stream_set_blocking($this->_mainSocket, 0); + + // Register a listener to be notified when server socket is ready to read. + if (self::$globalEvent) { + if ($this->transport !== 'udp') { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, + array($this, 'acceptUdpConnection')); + } + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (self::$eventLoopClass) { + return self::$eventLoopClass; + } + + $loop_name = ''; + foreach (self::$_availableEventLoops as $name=>$class) { + if (extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + if (interface_exists('\React\EventLoop\LoopInterface')) { + switch ($loop_name) { + case 'libevent': + self::$eventLoopClass = '\Workerman\Events\React\LibEventLoop'; + break; + case 'event': + self::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop'; + break; + default : + self::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop'; + break; + } + } else { + self::$eventLoopClass = self::$_availableEventLoops[$loop_name]; + } + } else { + self::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select'; + } + return self::$eventLoopClass; + } + + /** + * 获得 socket name + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? $this->_socketName : 'none'; + } + + /** + * 运行worker实例 + */ + public function run() + { + // 设置自动加载根目录 + Autoloader::setRootPath($this->_autoloadRootPath); + + // Create a global event loop. + if (!self::$globalEvent) { + $event_loop_class = self::getEventLoopName(); + self::$globalEvent = new $event_loop_class; + } + + // 监听_mainSocket上的可读事件(客户端连接事件) + if($this->_socketName) + { + if($this->transport !== 'udp') + { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } + else + { + self::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } + } + + // 用全局事件轮询初始化定时器 + Timer::init(self::$globalEvent); + + // 如果有设置进程启动回调,则执行 + if($this->onWorkerStart) + { + call_user_func($this->onWorkerStart, $this); + } + + // 子进程主循环 + self::$globalEvent->loop(); + } + + /** + * 停止当前worker实例 + * @return void + */ + public function stop() + { + // 如果有设置进程终止回调,则执行 + if($this->onWorkerStop) + { + call_user_func($this->onWorkerStop, $this); + } + // 删除相关监听事件,关闭_mainSocket + self::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + @fclose($this->_mainSocket); + } + + /** + * 接收一个客户端连接 + * @param resources $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + $new_socket = @stream_socket_accept($socket, 0, $remote_address); + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + } + + /** + * 处理udp连接(udp其实是无连接的,这里为保证和tcp连接接口一致) + * @param resource $socket + */ + public function acceptUdpConnection($socket) + { + $recv_buffer = stream_socket_recvfrom($socket, self::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $connection); + } + ConnectionInterface::$statistics['total_request']++; + try { + call_user_func($this->onMessage, $connection, $recv_buffer); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + return true; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/composer.json b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/composer.json new file mode 100644 index 0000000..b9a756f --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman-for-win/composer.json @@ -0,0 +1,30 @@ +{ + "name" : "workerman/workerman-for-win", + "type" : "project", + "keywords": ["event-loop", "asynchronous"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors" : [ + { + "name" : "walkor", + "email" : "walkor@workerman.net", + "homepage" : "http://www.workerman.net", + "role": "Developer" + } + ], + "support" : { + "email" : "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum" : "http://wenda.workerman.net/", + "wiki" : "http://doc3.workerman.net/index.html", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=5.3" + }, + "autoload": { + "psr-4": {"Workerman\\": "./"} + }, + "minimum-stability":"dev" +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman.log b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman.log new file mode 100644 index 0000000..57b29e2 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman.log @@ -0,0 +1,4 @@ +2018-11-26 16:59:31 pid:1 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://wiki.workerman.net/Error3 +2018-11-26 16:59:31 pid:1 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://wiki.workerman.net/Error3 +2018-11-26 16:59:38 pid:1 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://wiki.workerman.net/Error3 +2018-11-26 16:59:38 pid:1 SendBufferToWorker fail. The connections between Gateway and BusinessWorker are not ready. See http://wiki.workerman.net/Error3 diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/.gitignore b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/.gitignore new file mode 100644 index 0000000..f3f9e18 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/.gitignore @@ -0,0 +1,6 @@ +logs +.buildpath +.project +.settings +.idea +.DS_Store diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Autoloader.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Autoloader.php new file mode 100644 index 0000000..45773c9 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Autoloader.php @@ -0,0 +1,69 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +/** + * Autoload. + */ +class Autoloader +{ + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; + + /** + * Set autoload root path. + * + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_autoloadRootPath = $root_path; + } + + /** + * Load files by namespace. + * + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name); + if (strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !is_file($class_file)) { + $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + if (is_file($class_file)) { + require_once($class_file); + if (class_exists($name, false)) { + return true; + } + } + return false; + } +} + +spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php new file mode 100644 index 0000000..162c33a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/AsyncTcpConnection.php @@ -0,0 +1,369 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Remote port. + * + * @var int + */ + protected $_remotePort = 80; + + /** + * Connect start time. + * + * @var string + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + $address_info = parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2); + if (!$this->_remoteAddress) { + Worker::safeEcho(new \Exception('bad remote_address')); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 80; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; + $this->_remoteHost = $address_info['host']; + $this->_remotePort = $address_info['port']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + } + + $this->id = $this->_id = self::$_idRecorder++; + if(PHP_INT_MAX === self::$_idRecorder){ + self::$_idRecorder = 0; + } + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + self::$statistics['connection_count']++; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_contextOption = $context_option; + static::$connections[$this->_id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = microtime(true); + if ($this->transport !== 'unix') { + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = stream_context_create($this->_contextOption); + $this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT); + } + } else { + $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + STREAM_CLIENT_ASYNC_CONNECT); + } + // If failed attempt to emit onError callback. + if (!$this->_socket) { + $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reconnect($after = 0) + { + $this->_status = self::STATUS_INITIAL; + static::$connections[$this->_id] = $this; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * CancelReconnect. + */ + public function cancelReconnect() + { + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection() + { + if ($this->_status != self::STATUS_CONNECTING) { + return; + } + + // Remove EV_EXPECT for windows. + if(DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT); + } + + // Check socket state. + if ($address = stream_socket_get_name($this->_socket, true)) { + // Nonblocking. + stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = socket_import_stream($this->_socket); + socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); + socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); + } + + // Remove write listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // SSL handshake. + if ($this->transport === 'ssl') { + $this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket); + } else { + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } + + // Register a listener waiting read event. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onConnect + if (method_exists($this->protocol, 'onConnect')) { + try { + call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } else { + // Connection failed. + $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php new file mode 100644 index 0000000..62dca92 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/AsyncUdpConnection.php @@ -0,0 +1,206 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Emitted when socket connection closed. + * + * @var callback + */ + public $onClose = null; + + /** + * Connected or not. + * + * @var bool + */ + protected $connected = false; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = substr($address, 2); + $this->_contextOption = $context_option; + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ConnectionInterface::$statistics['total_request']++; + try { + call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + if ($this->connected === false) { + $this->connect(); + } + return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0); + } + + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + fclose($this->_socket); + $this->connected = false; + // Try to emit onClose callback. + if ($this->onClose) { + try { + call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->onConnect = $this->onMessage = $this->onClose = null; + return true; + } + + /** + * Connect. + * + * @return void + */ + public function connect() + { + if ($this->connected === true) { + return; + } + if ($this->_contextOption) { + $context = stream_context_create($this->_contextOption); + $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg, + 30, STREAM_CLIENT_CONNECT, $context); + } else { + $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg); + } + + if (!$this->_socket) { + Worker::safeEcho(new \Exception($errmsg)); + return; + } + if ($this->onMessage) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + $this->connected = true; + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php new file mode 100644 index 0000000..5ddd2f8 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/ConnectionInterface.php @@ -0,0 +1,125 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get local IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get local port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get local address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/TcpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/TcpConnection.php new file mode 100644 index 0000000..927a0d3 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/TcpConnection.php @@ -0,0 +1,979 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callback + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callback + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Maximum acceptable packet size. + * + * @var int + */ + public static $maxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + + /** + * Adding support of custom functions within protocols + * + * @param string $name + * @param array $arguments + * @return void + */ + public function __call($name, $arguments) { + // Try to emit custom function within protocol + if (method_exists($this->protocol, $name)) { + try { + return call_user_func(array($this->protocol, $name), $this, $arguments); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + self::$statistics['connection_count']++; + $this->id = $this->_id = self::$_idRecorder++; + if(self::$_idRecorder === PHP_INT_MAX){ + self::$_idRecorder = 0; + } + $this->_socket = $socket; + stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer) { + if ($this->bufferIsFull()) { + self::$statistics['send_fail']++; + return false; + } + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return null; + } + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + if ($this->transport === 'ssl') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + $this->_sendBuffer = $send_buffer; + $this->checkBufferWillFull(); + return null; + } + set_error_handler(function(){}); + $len = fwrite($this->_socket, $send_buffer); + restore_error_handler(); + // send successful. + if ($len === strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!is_resource($this->_socket) || feof($this->_socket)) { + self::$statistics['send_fail']++; + if ($this->onError) { + try { + call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return null; + } else { + if ($this->bufferIsFull()) { + self::$statistics['send_fail']++; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return ''; + } + return substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)substr(strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + if ($this->doSslHandshake($socket)) { + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } else { + return; + } + } + + set_error_handler(function(){}); + $buffer = fread($socket, self::READ_BUFFER_SIZE); + restore_error_handler(); + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (feof($socket) || !is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + restore_error_handler(); + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= static::$maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + Worker::safeEcho('error package. package_length=' . var_export($this->_currentPackageLength, true)); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + self::$statistics['total_request']++; + // The current packet length is equal to the length of the buffer. + if (strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + self::$statistics['total_request']++; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + set_error_handler(function(){}); + if ($this->transport === 'ssl') { + $len = fwrite($this->_socket, $this->_sendBuffer, 8192); + } else { + $len = fwrite($this->_socket, $this->_sendBuffer); + } + restore_error_handler(); + if ($len === strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = substr($this->_sendBuffer, $len); + } else { + self::$statistics['send_fail']++; + $this->destroy(); + } + } + + /** + * SSL handshake. + * + * @param $socket + * @return bool + */ + public function doSslHandshake($socket){ + if (feof($socket)) { + $this->destroy(); + return false; + } + $async = $this instanceof AsyncTcpConnection; + if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER; + } + + // Hidden error. + set_error_handler(function($errno, $errstr, $file){ + if (!Worker::$daemonize) { + Worker::safeEcho("SSL handshake error: $errstr \n"); + } + }); + $ret = stream_socket_enable_crypto($socket, true, $type); + restore_error_handler(); + // Negotiation has failed. + if (false === $ret) { + $this->destroy(); + return false; + } elseif (0 === $ret) { + // There isn't enough data and should try again. + return false; + } + if (isset($this->onSslHandshake)) { + try { + call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param TcpConnection $dest + * @return void + */ + public function pipe($dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->destroy(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } else { + if ($data !== null) { + $this->send($data, $raw); + } + $this->_status = self::STATUS_CLOSING; + } + if ($this->_sendBuffer === '') { + $this->destroy(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + return false; + } + + /** + * Whether send buffer is Empty. + * + * @return bool + */ + public function bufferIsEmpty() + { + return empty($this->_sendBuffer); + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // Close socket. + set_error_handler(function(){}); + fclose($this->_socket); + restore_error_handler(); + + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onClose + if ($this->protocol && method_exists($this->protocol, 'onClose')) { + try { + call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + static $mod; + self::$statistics['connection_count']--; + if (Worker::getGracefulStop()) { + if (!isset($mod)) { + $mod = ceil((self::$statistics['connection_count'] + 1) / 3); + } + + if (0 === self::$statistics['connection_count'] % $mod) { + Worker::log('worker[' . posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)'); + } + + if(0 === self::$statistics['connection_count']) { + Worker::stopAll(); + } + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/UdpConnection.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/UdpConnection.php new file mode 100644 index 0000000..2e7aeee --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Connection/UdpConnection.php @@ -0,0 +1,191 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return trim(substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return ''; + } + return substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)substr(strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Ev.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Ev.php new file mode 100644 index 0000000..1e6471d --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Ev.php @@ -0,0 +1,194 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param \EvWatcher $event + */ + public function timerCallback($event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_allEvents as $event) { + $event->stop(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Event.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Event.php new file mode 100644 index 0000000..b4d371a --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Event.php @@ -0,0 +1,219 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + if (class_exists('\\\\EventBase', false)) { + $class_name = '\\\\EventBase'; + } else { + $class_name = '\EventBase'; + } + $this->_eventBase = new $class_name(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + if (class_exists('\\\\Event', false)) { + $class_name = '\\\\Event'; + } else { + $class_name = '\Event'; + } + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = $class_name::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST; + $event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + $event->del(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/EventInterface.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/EventInterface.php new file mode 100644 index 0000000..88f38f2 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/EventInterface.php @@ -0,0 +1,107 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param mixed $args + * @return bool + */ + public function add($fd, $flag, $func, $args = null); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); + + /** + * Get Timer count. + * + * @return mixed + */ + public function getTimerCount(); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Libevent.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Libevent.php new file mode 100644 index 0000000..0b3f7c0 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Libevent.php @@ -0,0 +1,227 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = EV_SIGNAL | EV_PERSIST; + $this->_eventSignal[$fd_key] = event_new(); + if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = event_new(); + $timer_id = (int)$event; + if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; + + $event = event_new(); + + if (!event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + event_del($event); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} + diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/Base.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/Base.php new file mode 100644 index 0000000..d558b88 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/Base.php @@ -0,0 +1,262 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; +use React\EventLoop\TimerInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class Base implements \React\EventLoop\LoopInterface +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * @var array + */ + protected $_signalHandlerMap = array(); + + /** + * @var \React\EventLoop\LoopInterface + */ + protected $_eventLoop = null; + + /** + * Base constructor. + */ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } + + /** + * Add event listener to event loop. + * + * @param $fd + * @param $flag + * @param $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + if (isset($this->_signalHandlerMap[$fd])) { + $this->removeSignal($fd, $this->_signalHandlerMap[$fd]); + } + $this->_signalHandlerMap[$fd] = $func; + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + if (!isset($this->_eventLoop[$fd])) { + return false; + } + $func = $this->_eventLoop[$fd]; + unset($this->_eventLoop[$fd]); + return $this->removeSignal($fd, $func); + + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE: + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_timerIdMap); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addReadStream($stream, $listener) + { + return $this->_eventLoop->addReadStream($stream, $listener); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addWriteStream($stream, $listener) + { + return $this->_eventLoop->addWriteStream($stream, $listener); + } + + /** + * @param resource $stream + */ + public function removeReadStream($stream) + { + return $this->_eventLoop->removeReadStream($stream); + } + + /** + * @param resource $stream + */ + public function removeWriteStream($stream) + { + return $this->_eventLoop->removeWriteStream($stream); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addTimer($interval, $callback) + { + return $this->_eventLoop->addTimer($interval, $callback); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addPeriodicTimer($interval, $callback) + { + return $this->_eventLoop->addPeriodicTimer($interval, $callback); + } + + /** + * @param TimerInterface $timer + */ + public function cancelTimer(TimerInterface $timer) + { + return $this->_eventLoop->cancelTimer($timer); + } + + /** + * @param callable $listener + */ + public function futureTick($listener) + { + return $this->_eventLoop->futureTick($listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function addSignal($signal, $listener) + { + return $this->_eventLoop->addSignal($signal, $listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function removeSignal($signal, $listener) + { + return $this->_eventLoop->removeSignal($signal, $listener); + } + + /** + * Run. + */ + public function run() + { + return $this->_eventLoop->run(); + } + + /** + * Stop. + */ + public function stop() + { + return $this->_eventLoop->stop(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php new file mode 100644 index 0000000..3dab25b --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/ExtEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends Base +{ + + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtEventLoop(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php new file mode 100644 index 0000000..eb02b35 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/ExtLibEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtLibEventLoop + * @package Workerman\Events\React + */ +class ExtLibEventLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtLibeventLoop(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php new file mode 100644 index 0000000..7f5f94b --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/React/StreamSelectLoop.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Select.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Select.php new file mode 100644 index 0000000..a1e2cf0 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Select.php @@ -0,0 +1,341 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. + $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, + STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + if($this->channel) { + stream_set_blocking($this->channel[0], 0); + $this->_readFds[0] = $this->channel[0]; + } + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $count = $flag === self::EV_READ ? count($this->_readFds) : count($this->_writeFds); + if ($count >= 1024) { + echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n"; + } else if (DIRECTORY_SEPARATOR !== '/' && $count >= 256) { + echo "Warning: system call select exceeded the maximum number of connections 256.\n"; + } + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + if ($flag === self::EV_READ) { + $this->_readFds[$fd_key] = $fd; + } else { + $this->_writeFds[$fd_key] = $fd; + } + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - microtime(true)) * 1000000; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = microtime(true); + $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $this->_scheduler->insert($timer_id, -$next_run_time); + } + call_user_func_array($task_data[0], $task_data[1]); + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + continue; + } + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + $e = null; + while (1) { + if(DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_exceptFds; + + // Waiting read/write/signal/timeout events. + set_error_handler(function(){}); + $ret = stream_select($read, $write, $except, 0, $this->_selectTimeout); + restore_error_handler(); + + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Swoole.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Swoole.php new file mode 100644 index 0000000..baefdb9 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Events/Swoole.php @@ -0,0 +1,216 @@ + + * @link http://www.workerman.net/ + * @link https://github.com/ares333/Workerman + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Swoole\Event; +use Swoole\Timer; + +class Swoole implements EventInterface +{ + + protected $_timer = array(); + + protected $_timerOnceMap = array(); + + protected $_fd = array(); + + // milisecond + public static $signalDispatchInterval = 200; + + protected $_hasSignal = false; + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::add() + */ + public function add($fd, $flag, $func, $args = null) + { + if (! isset($args)) { + $args = array(); + } + switch ($flag) { + case self::EV_SIGNAL: + $res = pcntl_signal($fd, $func, false); + if (! $this->_hasSignal && $res) { + Timer::tick(static::$signalDispatchInterval, + function () { + pcntl_signal_dispatch(); + }); + $this->_hasSignal = true; + } + return $res; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $method = self::EV_TIMER == $flag ? 'tick' : 'after'; + $mapId = count($this->_timerOnceMap); + $timer_id = Timer::$method($fd * 1000, + function ($timer_id = null) use ($func, $args, $mapId) { + call_user_func_array($func, $args); + // EV_TIMER_ONCE + if (! isset($timer_id)) { + // may be deleted in $func + if (array_key_exists($mapId, $this->_timerOnceMap)) { + $timer_id = $this->_timerOnceMap[$mapId]; + unset($this->_timer[$timer_id], + $this->_timerOnceMap[$mapId]); + } + } + }); + if ($flag == self::EV_TIMER_ONCE) { + $this->_timerOnceMap[$mapId] = $timer_id; + $this->_timer[$timer_id] = $mapId; + } else { + $this->_timer[$timer_id] = null; + } + return $timer_id; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (! isset($this->_fd[$fd_key])) { + if ($flag == self::EV_READ) { + $res = Event::add($fd, $func, null, SWOOLE_EVENT_READ); + $fd_type = SWOOLE_EVENT_READ; + } else { + $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); + $fd_type = SWOOLE_EVENT_WRITE; + } + if ($res) { + $this->_fd[$fd_key] = $fd_type; + } + } else { + $fd_val = $this->_fd[$fd_key]; + $res = true; + if ($flag == self::EV_READ) { + if (($fd_val & SWOOLE_EVENT_READ) != SWOOLE_EVENT_READ) { + $res = Event::set($fd, $func, null, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; + } + } else { + if (($fd_val & SWOOLE_EVENT_WRITE) != SWOOLE_EVENT_WRITE) { + $res = Event::set($fd, null, $func, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; + } + } + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_SIGNAL: + return pcntl_signal($fd, SIG_IGN, false); + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // already remove in EV_TIMER_ONCE callback. + if (! array_key_exists($fd, $this->_timer)) { + return true; + } + $res = Timer::clear($fd); + if ($res) { + $mapId = $this->_timer[$fd]; + if (isset($mapId)) { + unset($this->_timerOnceMap[$mapId]); + } + unset($this->_timer[$fd]); + } + return $res; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (isset($this->_fd[$fd_key])) { + $fd_val = $this->_fd[$fd_key]; + if ($flag == self::EV_READ) { + $flag_remove = ~ SWOOLE_EVENT_READ; + } else { + $flag_remove = ~ SWOOLE_EVENT_WRITE; + } + $fd_val &= $flag_remove; + if (0 === $fd_val) { + $res = Event::del($fd); + if ($res) { + unset($this->_fd[$fd_key]); + } + } else { + $res = Event::set($fd, null, null, $fd_val); + if ($res) { + $this->_fd[$fd_key] = $fd_val; + } + } + } else { + $res = true; + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::clearAllTimer() + */ + public function clearAllTimer() + { + foreach (array_keys($this->_timer) as $v) { + Timer::clear($v); + } + $this->_timer = array(); + $this->_timerOnceMap = array(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::loop() + */ + public function loop() + { + Event::wait(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::destroy() + */ + public function destroy() + { + //Event::exit(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::getTimerCount() + */ + public function getTimerCount() + { + return count($this->_timer); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Lib/Constants.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Lib/Constants.php new file mode 100644 index 0000000..5048a39 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Lib/Constants.php @@ -0,0 +1,44 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +// Date.timezone +if (!ini_get('date.timezone')) { + date_default_timezone_set('Asia/Shanghai'); +} +// Display errors. +ini_set('display_errors', 'on'); +// Reporting all. +error_reporting(E_ALL); + +// Reset opcache. +if (function_exists('opcache_reset')) { + opcache_reset(); +} + +// For onError callback. +define('WORKERMAN_CONNECT_FAIL', 1); +// For onError callback. +define('WORKERMAN_SEND_FAIL', 2); + +// Define OS Type +define('OS_TYPE_LINUX', 'linux'); +define('OS_TYPE_WINDOWS', 'windows'); + +// Compatible with php7 +if(!class_exists('Error')) +{ + class Error extends Exception + { + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Lib/Timer.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Lib/Timer.php new file mode 100644 index 0000000..2dc2302 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Lib/Timer.php @@ -0,0 +1,179 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * Timer. + * + * example: + * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var \Workerman\Events\EventInterface + */ + protected static $_event = null; + + /** + * Init. + * + * @param \Workerman\Events\EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + } else { + if (function_exists('pcntl_signal')) { + pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param float $time_interval + * @param callable $func + * @param mixed $args + * @param bool $persistent + * @return int/false + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + Worker::safeEcho(new Exception("bad time_interval")); + return false; + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + if (!is_callable($func)) { + Worker::safeEcho(new Exception("not callable")); + return false; + } + + if (empty(self::$_tasks)) { + pcntl_alarm(1); + } + + $time_now = time(); + $run_time = $time_now + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); + return 1; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + pcntl_alarm(0); + return; + } + + $time_now = time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + Worker::safeEcho($e); + } + if ($persistent) { + self::add($time_interval, $task_func, $task_args); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + return false; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = array(); + pcntl_alarm(0); + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/MIT-LICENSE.txt b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Frame.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Frame.php new file mode 100644 index 0000000..4a6b13e --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (strlen($buffer) < 4) { + return 0; + } + $unpack_data = unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + strlen($buffer); + return pack('N', $total_length) . $buffer; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Http.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Http.php new file mode 100644 index 0000000..0ff125b --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Http.php @@ -0,0 +1,701 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * http protocol + */ +class Http +{ + /** + * The supported HTTP methods + * @var array + */ + public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'); + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + if (!strpos($recv_buffer, "\r\n\r\n")) { + // Judge whether the package length exceeds the limit. + if (strlen($recv_buffer) >= $connection::$maxPackageSize) { + $connection->close(); + return 0; + } + return 0; + } + + list($header,) = explode("\r\n\r\n", $recv_buffer, 2); + $method = substr($header, 0, strpos($header, ' ')); + + if(in_array($method, static::$methods)) { + return static::getRequestSize($header, $method); + }else{ + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + } + + /** + * Get whole size of the request + * includes the request headers and request body. + * @param string $header The request headers + * @param string $method The request method + * @return integer + */ + protected static function getRequestSize($header, $method) + { + if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') { + return strlen($header) + 4; + } + $match = array(); + if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { + $content_length = isset($match[1]) ? $match[1] : 0; + return $content_length + strlen($header) + 4; + } + return $method === 'DELETE' ? strlen($header) + 4 : 0; + } + + /** + * Parse $_POST、$_GET、$_COOKIE. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return array + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + // Init. + $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); + $GLOBALS['HTTP_RAW_POST_DATA'] = ''; + // Clear cache. + HttpCache::$header = array('Connection' => 'Connection: keep-alive'); + HttpCache::$instance = new HttpCache(); + // $_SERVER + $_SERVER = array( + 'QUERY_STRING' => '', + 'REQUEST_METHOD' => '', + 'REQUEST_URI' => '', + 'SERVER_PROTOCOL' => '', + 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION, + 'SERVER_NAME' => '', + 'HTTP_HOST' => '', + 'HTTP_USER_AGENT' => '', + 'HTTP_ACCEPT' => '', + 'HTTP_ACCEPT_LANGUAGE' => '', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_COOKIE' => '', + 'HTTP_CONNECTION' => '', + 'CONTENT_TYPE' => '', + 'REMOTE_ADDR' => '', + 'REMOTE_PORT' => '0', + 'REQUEST_TIME' => time() + ); + + // Parse headers. + list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); + $header_data = explode("\r\n", $http_header); + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + $http_post_boundary = ''; + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + // content-type + case 'CONTENT_TYPE': + if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { + if ($pos = strpos($value, ';')) { + $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos); + } else { + $_SERVER['CONTENT_TYPE'] = $value; + } + } else { + $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; + $http_post_boundary = '--' . $match[1]; + } + break; + case 'CONTENT_LENGTH': + $_SERVER['CONTENT_LENGTH'] = $value; + break; + case 'UPGRADE': + if($value=='websocket'){ + $connection->protocol = "\\Workerman\\Protocols\\Websocket"; + return \Workerman\Protocols\Websocket::input($recv_buffer,$connection); + } + break; + } + } + if(isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE){ + HttpCache::$gzip = true; + } + // Parse $_POST. + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_SERVER['CONTENT_TYPE'])) { + switch ($_SERVER['CONTENT_TYPE']) { + case 'multipart/form-data': + self::parseUploadFiles($http_body, $http_post_boundary); + break; + case 'application/json': + $_POST = json_decode($http_body, true); + break; + case 'application/x-www-form-urlencoded': + parse_str($http_body, $_POST); + break; + } + } + } + + // Parse other HTTP action parameters + if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != "POST") { + $data = array(); + if ($_SERVER['CONTENT_TYPE'] === "application/x-www-form-urlencoded") { + parse_str($http_body, $data); + } elseif ($_SERVER['CONTENT_TYPE'] === "application/json") { + $data = json_decode($http_body, true); + } + $_REQUEST = array_merge($_REQUEST, $data); + } + + // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA + $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + + if (is_array($_POST)) { + // REQUEST + $_REQUEST = array_merge($_GET, $_POST, $_REQUEST); + } else { + // REQUEST + $_REQUEST = array_merge($_GET, $_REQUEST); + } + + // REMOTE_ADDR REMOTE_PORT + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + + return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); + } + + /** + * Http encode. + * + * @param string $content + * @param TcpConnection $connection + * @return string + */ + public static function encode($content, TcpConnection $connection) + { + // Default http-code. + if (!isset(HttpCache::$header['Http-Code'])) { + $header = "HTTP/1.1 200 OK\r\n"; + } else { + $header = HttpCache::$header['Http-Code'] . "\r\n"; + unset(HttpCache::$header['Http-Code']); + } + + // Content-Type + if (!isset(HttpCache::$header['Content-Type'])) { + $header .= "Content-Type: text/html;charset=utf-8\r\n"; + } + + // other headers + foreach (HttpCache::$header as $key => $item) { + if ('Set-Cookie' === $key && is_array($item)) { + foreach ($item as $it) { + $header .= $it . "\r\n"; + } + } else { + $header .= $item . "\r\n"; + } + } + if(HttpCache::$gzip && isset($connection->gzip) && $connection->gzip){ + $header .= "Content-Encoding: gzip\r\n"; + $content = gzencode($content,$connection->gzip); + } + // header + $header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; + + // save session + self::sessionWriteClose(); + + // the whole http package + return $header . $content; + } + + /** + * 设置http头 + * + * @return bool|void + */ + public static function header($content, $replace = true, $http_response_code = 0) + { + if (PHP_SAPI != 'cli') { + return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); + } + if (strpos($content, 'HTTP') === 0) { + $key = 'Http-Code'; + } else { + $key = strstr($content, ":", true); + if (empty($key)) { + return false; + } + } + + if ('location' === strtolower($key) && !$http_response_code) { + return self::header($content, true, 302); + } + + if (isset(HttpCache::$codes[$http_response_code])) { + HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; + if ($key === 'Http-Code') { + return true; + } + } + + if ($key === 'Set-Cookie') { + HttpCache::$header[$key][] = $content; + } else { + HttpCache::$header[$key] = $content; + } + + return true; + } + + /** + * Remove header. + * + * @param string $name + * @return void + */ + public static function headerRemove($name) + { + if (PHP_SAPI != 'cli') { + header_remove($name); + return; + } + unset(HttpCache::$header[$name]); + } + + /** + * Set cookie. + * + * @param string $name + * @param string $value + * @param integer $maxage + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $HTTPOnly + * @return bool|void + */ + public static function setcookie( + $name, + $value = '', + $maxage = 0, + $path = '', + $domain = '', + $secure = false, + $HTTPOnly = false + ) { + if (PHP_SAPI != 'cli') { + return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); + } + return self::header( + 'Set-Cookie: ' . $name . '=' . rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . (empty($maxage) ? '' : '; Max-Age=' . $maxage) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$HTTPOnly ? '' : '; HttpOnly'), false); + } + + /** + * sessionCreateId + * + * @return string + */ + public static function sessionCreateId() + { + mt_srand(); + return bin2hex(pack('d', microtime(true)) . pack('N',mt_rand(0, 2147483647))); + } + + /** + * sessionId + * + * @param string $id + * + * @return string|null + */ + public static function sessionId($id = null) + { + if (PHP_SAPI != 'cli') { + return $id ? session_id($id) : session_id(); + } + if (static::sessionStarted() && HttpCache::$instance->sessionFile) { + return str_replace('sess_', '', basename(HttpCache::$instance->sessionFile)); + } + return ''; + } + + /** + * sessionName + * + * @param string $name + * + * @return string + */ + public static function sessionName($name = null) + { + if (PHP_SAPI != 'cli') { + return $name ? session_name($name) : session_name(); + } + $session_name = HttpCache::$sessionName; + if ($name && ! static::sessionStarted()) { + HttpCache::$sessionName = $name; + } + return $session_name; + } + + /** + * sessionSavePath + * + * @param string $path + * + * @return void + */ + public static function sessionSavePath($path = null) + { + if (PHP_SAPI != 'cli') { + return $path ? session_save_path($path) : session_save_path(); + } + if ($path && is_dir($path) && is_writable($path) && !static::sessionStarted()) { + HttpCache::$sessionPath = $path; + } + return HttpCache::$sessionPath; + } + + /** + * sessionStarted + * + * @return bool + */ + public static function sessionStarted() + { + if (!HttpCache::$instance) return false; + + return HttpCache::$instance->sessionStarted; + } + + /** + * sessionStart + * + * @return bool + */ + public static function sessionStart() + { + if (PHP_SAPI != 'cli') { + return session_start(); + } + + self::tryGcSessions(); + + if (HttpCache::$instance->sessionStarted) { + Worker::safeEcho("already sessionStarted\n"); + return true; + } + HttpCache::$instance->sessionStarted = true; + // Generate a SID. + if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName])) { + // Create a unique session_id and the associated file name. + while (true) { + $session_id = static::sessionCreateId(); + if (!is_file($file_name = HttpCache::$sessionPath . '/sess_' . $session_id)) break; + } + HttpCache::$instance->sessionFile = $file_name; + return self::setcookie( + HttpCache::$sessionName + , $session_id + , ini_get('session.cookie_lifetime') + , ini_get('session.cookie_path') + , ini_get('session.cookie_domain') + , ini_get('session.cookie_secure') + , ini_get('session.cookie_httponly') + ); + } + if (!HttpCache::$instance->sessionFile) { + HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/sess_' . $_COOKIE[HttpCache::$sessionName]; + } + // Read session from session file. + if (HttpCache::$instance->sessionFile) { + $raw = file_get_contents(HttpCache::$instance->sessionFile); + if ($raw) { + $_SESSION = unserialize($raw); + } + } + return true; + } + + /** + * Save session. + * + * @return bool + */ + public static function sessionWriteClose() + { + if (PHP_SAPI != 'cli') { + return session_write_close(); + } + if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { + $session_str = serialize($_SESSION); + if ($session_str && HttpCache::$instance->sessionFile) { + return file_put_contents(HttpCache::$instance->sessionFile, $session_str); + } + } + return empty($_SESSION); + } + + /** + * End, like call exit in php-fpm. + * + * @param string $msg + * @throws \Exception + */ + public static function end($msg = '') + { + if (PHP_SAPI != 'cli') { + exit($msg); + } + if ($msg) { + echo $msg; + } + throw new \Exception('jump_exit'); + } + + /** + * Get mime types. + * + * @return string + */ + public static function getMimeTypesFile() + { + return __DIR__ . '/Http/mime.types'; + } + + /** + * Parse $_FILES. + * + * @param string $http_body + * @param string $http_post_boundary + * @return void + */ + protected static function parseUploadFiles($http_body, $http_post_boundary) + { + $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); + $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); + if ($boundary_data_array[0] === '') { + unset($boundary_data_array[0]); + } + $key = -1; + foreach ($boundary_data_array as $boundary_data_buffer) { + list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); + // Remove \r\n from the end of buffer. + $boundary_value = substr($boundary_value, 0, -2); + $key ++; + foreach (explode("\r\n", $boundary_header_buffer) as $item) { + list($header_key, $header_value) = explode(": ", $item); + $header_key = strtolower($header_key); + switch ($header_key) { + case "content-disposition": + // Is file data. + if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { + // Parse $_FILES. + $_FILES[$key] = array( + 'name' => $match[1], + 'file_name' => $match[2], + 'file_data' => $boundary_value, + 'file_size' => strlen($boundary_value), + ); + continue; + } // Is post field. + else { + // Parse $_POST. + if (preg_match('/name="(.*?)"$/', $header_value, $match)) { + $_POST[$match[1]] = $boundary_value; + } + } + break; + case "content-type": + // add file_type + $_FILES[$key]['file_type'] = trim($header_value); + break; + } + } + } + } + + /** + * Try GC sessions. + * + * @return void + */ + public static function tryGcSessions() + { + if (HttpCache::$sessionGcProbability <= 0 || + HttpCache::$sessionGcDivisor <= 0 || + rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { + return; + } + + $time_now = time(); + foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { + if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { + unlink($file); + } + } + } +} + +/** + * Http cache for the current http response. + */ +class HttpCache +{ + public static $codes = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + + /** + * @var HttpCache + */ + public static $instance = null; + public static $header = array(); + public static $gzip = false; + public static $sessionPath = ''; + public static $sessionName = ''; + public static $sessionGcProbability = 1; + public static $sessionGcDivisor = 1000; + public static $sessionGcMaxLifeTime = 1440; + public $sessionStarted = false; + public $sessionFile = ''; + + public static function init() + { + if (!self::$sessionName) { + self::$sessionName = ini_get('session.name'); + } + + if (!self::$sessionPath) { + self::$sessionPath = @session_save_path(); + } + + if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) { + self::$sessionPath = sys_get_temp_dir(); + } + + if ($gc_probability = ini_get('session.gc_probability')) { + self::$sessionGcProbability = $gc_probability; + } + + if ($gc_divisor = ini_get('session.gc_divisor')) { + self::$sessionGcDivisor = $gc_divisor; + } + + if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) { + self::$sessionGcMaxLifeTime = $gc_max_life_time; + } + } +} + +HttpCache::init(); diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Http/mime.types b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Http/mime.types new file mode 100644 index 0000000..8a218b2 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Http/mime.types @@ -0,0 +1,80 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php new file mode 100644 index 0000000..9afe984 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param ConnectionInterface $connection + * @param string $recv_buffer + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param ConnectionInterface $connection + * @param string $recv_buffer + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param ConnectionInterface $connection + * @param mixed $data + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Text.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Text.php new file mode 100644 index 0000000..5c78852 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + // Judge whether the package length exceeds the limit. + if (strlen($buffer) >= $connection::$maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return trim($buffer); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Websocket.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Websocket.php new file mode 100644 index 0000000..7ebf591 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Websocket.php @@ -0,0 +1,504 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = strlen($buffer); + // We need more data. + if ($recv_len < 6) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $firstbyte = ord($buffer[0]); + $secondbyte = ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if (!$masked) { + Worker::safeEcho("frame not masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { + try { + call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close("\x88\x02\x27\x10", true); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"); + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $data_len = $arr['c1']*4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection::$maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { + try { + call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection, $ping_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { + try { + call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection, $pong_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); + } + $len = strlen($buffer); + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $first_byte = $connection->websocketType; + + if ($len <= 125) { + $encode_buffer = $first_byte . chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->websocketHandshake)) { + if (empty($connection->tmpWebsocketData)) { + $connection->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $masks = $data = $decoded = ''; + $len = ord($buffer[1]) & 127; + if ($len === 126) { + $masks = substr($buffer, 4, 4); + $data = substr($buffer, 8); + } else { + if ($len === 127) { + $masks = substr($buffer, 10, 4); + $data = substr($buffer, 14); + } else { + $masks = substr($buffer, 2, 4); + $data = substr($buffer, 6); + } + } + for ($index = 0; $index < strlen($data); $index++) { + $decoded .= $data[$index] ^ $masks[$index % 4]; + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded = $connection->websocketDataBuffer . $decoded; + $connection->websocketDataBuffer = ''; + } + return $decoded; + } + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + protected static function dealHandshake($buffer, $connection) + { + // HTTP protocol. + if (0 === strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $heder_end_pos = strpos($buffer, "\r\n\r\n"); + if (!$heder_end_pos) { + return 0; + } + $header_length = $heder_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Sec-WebSocket-Key not found.
This is a WebSocket service and can not be accessed via HTTP.
See http://wiki.workerman.net/Error1 for detail.", + true); + $connection->close(); + return 0; + } + // Calculation websocket key. + $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; + $handshake_message .= "Upgrade: websocket\r\n"; + $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; + $handshake_message .= "Connection: Upgrade\r\n"; + $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n"; + + // Websocket data buffer. + $connection->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $has_server_header = false; + + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { + static::parseHttpHeader($buffer); + try { + call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + + if (isset($connection->headers)) { + if (is_array($connection->headers)) { + foreach ($connection->headers as $header) { + if (strpos($header, 'Server:') === 0) { + $has_server_header = true; + } + $handshake_message .= "$header\r\n"; + } + } else { + $handshake_message .= "$connection->headers\r\n"; + } + } + } + if (!$has_server_header) { + $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; + } + $handshake_message .= "\r\n"; + // Send handshake response. + $connection->send($handshake_message, true); + // Mark handshake complete.. + $connection->websocketHandshake = true; + // There are data waiting to be sent. + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (strlen($buffer) > $header_length) { + return static::input(substr($buffer, $header_length), $connection); + } + return 0; + } // Is flash policy-file-request. + elseif (0 === strpos($buffer, 'send($policy_xml, true); + $connection->consumeRecvBuffer(strlen($buffer)); + return 0; + } + // Bad websocket handshake request. + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n400 Bad Request
Invalid handshake data for websocket.
See http://wiki.workerman.net/Error1 for detail.", + true); + $connection->close(); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = explode("\r\n\r\n", $buffer, 2); + $header_data = explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Ws.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Ws.php new file mode 100644 index 0000000..e5a7d66 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Protocols/Ws.php @@ -0,0 +1,470 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\TcpConnection; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, $connection) + { + if (empty($connection->handshakeStep)) { + Worker::safeEcho("recv data before handshake. Buffer:" . bin2hex($buffer) . "\n"); + return false; + } + // Recv handshake response + if ($connection->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recv_len = strlen($buffer); + if ($recv_len < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = ord($buffer[0]); + $secondbyte = ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if ($masked) { + Worker::safeEcho("frame masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"); + $connection->close(); + return 0; + } + // Calculate packet length. + if ($data_len === 126) { + if (strlen($buffer) < 4) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $current_frame_length = $pack['total_len'] + 4; + } else if ($data_len === 127) { + if (strlen($buffer) < 10) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; + } else { + $current_frame_length = $data_len + 2; + } + + $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection::$maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing)) { + try { + call_user_func($connection->onWebSocketPing, $connection, $ping_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + call_user_func($connection->onWebSocketPong, $connection, $pong_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->handshakeStep)) { + self::sendHandshake($connection); + } + $mask = 1; + $mask_key = "\x00\x00\x00\x00"; + + $pack = ''; + $length = $length_flag = strlen($payload); + if (65535 < $length) { + $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); + $length_flag = 127; + } else if (125 < $length) { + $pack = pack('n*', $length); + $length_flag = 126; + } + + $head = ($mask << 7) | $length_flag; + $head = $connection->websocketType . chr($head) . $pack; + + $frame = $head . $mask_key; + // append payload to frame: + for ($i = 0; $i < $length; $i++) { + $frame .= $payload[$i] ^ $mask_key[$i % 4]; + } + if ($connection->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, $connection) + { + $data_length = ord($bytes[1]); + + if ($data_length === 126) { + $decoded_data = substr($bytes, 4); + } else if ($data_length === 127) { + $decoded_data = substr($bytes, 10); + } else { + $decoded_data = substr($bytes, 2); + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded_data; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded_data = $connection->websocketDataBuffer . $decoded_data; + $connection->websocketDataBuffer = ''; + } + return $decoded_data; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + self::sendHandshake($connection); + } + + /** + * Clean + * + * @param $connection + */ + public static function onClose($connection) + { + $connection->handshakeStep = null; + $connection->websocketCurrentFrameLength = 0; + $connection->tmpWebsocketData = ''; + $connection->websocketDataBuffer = ''; + if (!empty($connection->websocketPingTimer)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param \Workerman\Connection\TcpConnection $connection + * @return void + */ + public static function sendHandshake($connection) + { + if (!empty($connection->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $connection->websocketSecKey = base64_encode(md5(mt_rand(), true)); + $userHeader = ''; + if (!empty($connection->wsHttpHeader)) { + if (is_array($connection->wsHttpHeader)){ + foreach($connection->wsHttpHeader as $k=>$v){ + $userHeader .= "$k: $v\r\n"; + } + }else{ + $userHeader .= $connection->wsHttpHeader; + } + $userHeader = "\r\n".trim($userHeader); + } + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". + "Host: $host\r\n". + "Connection: Upgrade\r\n". + "Upgrade: websocket\r\n". + "Origin: ". (isset($connection->websocketOrigin) ? $connection->websocketOrigin : '*') ."\r\n". + (isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":''). + "Sec-WebSocket-Version: 13\r\n". + "Sec-WebSocket-Key: " . $connection->websocketSecKey . $userHeader . "\r\n\r\n"; + $connection->send($header, true); + $connection->handshakeStep = 1; + $connection->websocketCurrentFrameLength = 0; + $connection->websocketDataBuffer = ''; + $connection->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + $pos = strpos($buffer, "\r\n\r\n"); + if ($pos) { + //checking Sec-WebSocket-Accept + if (preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { + if ($match[1] !== base64_encode(sha1($connection->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { + Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + } else { + Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + + // handshake complete + + // Get WebSocket subprotocol (if specified by server) + if (preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { + $connection->WSServerProtocol = trim($match[1]); + } + + $connection->handshakeStep = 2; + $handshake_response_length = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ + if (false === $connection->send(pack('H*', '898000000000'), true)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshake_response_length); + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (strlen($buffer) > $handshake_response_length) { + return self::input(substr($buffer, $handshake_response_length), $connection); + } + } + return 0; + } + + public static function WSSetProtocol($connection, $params) { + $connection->WSClientProtocol = $params[0]; + } + + public static function WSGetServerProtocol($connection) { + return (property_exists($connection, 'WSServerProtocol')?$connection->WSServerProtocol:null); + } + +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/README.md b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/README.md new file mode 100644 index 0000000..c0d4ddb --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/README.md @@ -0,0 +1,603 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) +[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) +[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) +[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) +[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) +[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) + +## What is it +Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications. Supports HTTP, Websocket, SSL and other custom protocols. Supports libevent, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react). + +## Requires +PHP 5.3 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions for PHP + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +```php +count = 4; + +// Emitted when new connection come +$ws_worker->onConnect = function($connection) +{ + echo "New connection\n"; + }; + +// Emitted when data received +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### http worker #### +$http_worker = new Worker("http://0.0.0.0:2345"); + +// 4 processes +$http_worker->count = 4; + +// Emitted when data received +$http_worker->onMessage = function($connection, $data) +{ + // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available + var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES); + // send data to client + $connection->send("hello world \n"); +}; + +// run all workers +Worker::runAll(); +``` + +### A WebServer +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\WebServer; +use Workerman\Worker; + +// WebServer +$web = new WebServer("http://0.0.0.0:80"); + +// 4 processes +$web->count = 4; + +// Set the root of domains +$web->addRoot('www.your_domain.com', '/your/path/Web'); +$web->addRoot('www.another_domain.com', '/another/path/Web'); +// run all workers +Worker::runAll(); +``` + +### A tcp server +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### create socket and listen 1234 port #### +$tcp_worker = new Worker("tcp://0.0.0.0:1234"); + +// 4 processes +$tcp_worker->count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function($connection) +{ + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello $data \n"); +}; + +// Emitted when new connection come +$tcp_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### Enable SSL +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false, + ) +); + +// Create a Websocket server with ssl context. +$ws_worker = new Worker("websocket://0.0.0.0:2346", $context); + +// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). +// The similar approaches for Https etc. +$ws_worker->transport = 'ssl'; + +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +namespace Protocols; +/** + * User defined protocol + * Format Text+"\n" + */ +class MyTextProtocol +{ + public static function input($recv_buffer) + { + // Find the position of the first occurrence of "\n" + $pos = strpos($recv_buffer, "\n"); + // Not a complete package. Return 0 because the length of package can not be calculated + if($pos === false) + { + return 0; + } + // Return length of the package + return $pos+1; + } + + public static function decode($recv_buffer) + { + return trim($recv_buffer); + } + + public static function encode($data) + { + return $data."\n"; + } +} +``` + +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### MyTextProtocol worker #### +$text_worker = new Worker("MyTextProtocol://0.0.0.0:5678"); + +$text_worker->onConnect = function($connection) +{ + echo "New connection\n"; +}; + +$text_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello world \n"); +}; + +$text_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// run all workers +Worker::runAll(); +``` + +### Timer +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +use Workerman\Lib\Timer; + +$task = new Worker(); +$task->onWorkerStart = function($task) +{ + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, + function() + { + echo "Timer run\n"; + } + ); +}; + +// run all workers +Worker::runAll(); +``` + +### AsyncTcpConnection (tcp/ws/text/frame etc...) +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +use Workerman\Connection\AsyncTcpConnection; + +$worker = new Worker(); +$worker->onWorkerStart = function() +{ + // Websocket protocol for client. + $ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80"); + $ws_connection->onConnect = function($connection){ + $connection->send('hello'); + }; + $ws_connection->onMessage = function($connection, $data){ + echo "recv: $data\n"; + }; + $ws_connection->onError = function($connection, $code, $msg){ + echo "error: $msg\n"; + }; + $ws_connection->onClose = function($connection){ + echo "connection closed\n"; + }; + $ws_connection->connect(); +}; +Worker::runAll(); +``` + +### Async Mysql of ReactPHP +``` +composer require react/mysql +``` + +```php +onWorkerStart = function() { + global $mysql; + $loop = Worker::getEventLoop(); + $mysql = new React\MySQL\Connection($loop, array( + 'host' => '127.0.0.1', + 'dbname' => 'dbname', + 'user' => 'user', + 'passwd' => 'passwd', + )); + $mysql->on('error', function($e){ + echo $e; + }); + $mysql->connect(function ($e) { + if($e) { + echo $e; + } else { + echo "connect success\n"; + } + }); +}; +$worker->onMessage = function($connection, $data) { + global $mysql; + $mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) { + if ($command->hasError()) { + $error = $command->getError(); + } else { + $results = $command->resultRows; + $fields = $command->resultFields; + $connection->send(json_encode($results)); + } + }); +}; +Worker::runAll(); +``` + +### Async Redis of ReactPHP +``` +composer require clue/redis-react +``` + +```php +onWorkerStart = function() { + global $factory; + $loop = Worker::getEventLoop(); + $factory = new Factory($loop); +}; + +$worker->onMessage = function($connection, $data) { + global $factory; + $factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) { + $client->set('greeting', 'Hello world'); + $client->append('greeting', '!'); + + $client->get('greeting')->then(function ($greeting) use ($connection){ + // Hello world! + echo $greeting . PHP_EOL; + $connection->send($greeting); + }); + + $client->incr('invocation')->then(function ($n) use ($connection){ + echo 'This is invocation #' . $n . PHP_EOL; + $connection->send($n); + }); + }); +}; + +Worker::runAll(); +``` + +### Aysnc dns of ReactPHP +``` +composer require react/dns +``` + +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +$worker = new Worker('tcp://0.0.0.0:6161'); +$worker->onWorkerStart = function() { + global $dns; + // Get event-loop. + $loop = Worker::getEventLoop(); + $factory = new React\Dns\Resolver\Factory(); + $dns = $factory->create('8.8.8.8', $loop); +}; +$worker->onMessage = function($connection, $host) { + global $dns; + $host = trim($host); + $dns->resolve($host)->then(function($ip) use($host, $connection) { + $connection->send("$host: $ip"); + },function($e) use($host, $connection){ + $connection->send("$host: {$e->getMessage()}"); + }); +}; + +Worker::runAll(); +``` + +### Http client of ReactPHP +``` +composer require react/http-client +``` + +```php +onMessage = function($connection, $host) { + $loop = Worker::getEventLoop(); + $client = new \React\HttpClient\Client($loop); + $request = $client->request('GET', trim($host)); + $request->on('error', function(Exception $e) use ($connection) { + $connection->send($e); + }); + $request->on('response', function ($response) use ($connection) { + $response->on('data', function ($data) use ($connection) { + $connection->send($data); + }); + }); + $request->end(); +}; + +Worker::runAll(); +``` + +### ZMQ of ReactPHP +``` +composer require react/zmq +``` + +```php +onWorkerStart = function() { + global $pull; + $loop = Worker::getEventLoop(); + $context = new React\ZMQ\Context($loop); + $pull = $context->getSocket(ZMQ::SOCKET_PULL); + $pull->bind('tcp://127.0.0.1:5555'); + + $pull->on('error', function ($e) { + var_dump($e->getMessage()); + }); + + $pull->on('message', function ($msg) { + echo "Received: $msg\n"; + }); +}; + +Worker::runAll(); +``` + +### STOMP of ReactPHP +``` +composer require react/stomp +``` + +```php +onWorkerStart = function() { + global $client; + $loop = Worker::getEventLoop(); + $factory = new React\Stomp\Factory($loop); + $client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest')); + + $client + ->connect() + ->then(function ($client) use ($loop) { + $client->subscribe('/topic/foo', function ($frame) { + echo "Message received: {$frame->body}\n"; + }); + }); +}; + +Worker::runAll(); +``` + + + +## Available commands +```php start.php start ``` +```php start.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php start.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php start.php connections``` +```php start.php stop ``` +```php start.php restart ``` +```php start.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](http://www.workerman.net) + +中文文档: [http://doc.workerman.net](http://doc.workerman.net) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) + +# Benchmarks +``` +CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally +Memory: 8G +OS: Ubuntu 14.04 LTS +Software: ab +PHP: 5.5.9 +``` + +**Codes** +```php +count=3; +$worker->onMessage = function($connection, $data) +{ + $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello"); +}; +Worker::runAll(); +``` +**Result** + +```shell +ab -n1000000 -c100 -k http://127.0.0.1:1234/ +This is ApacheBench, Version 2.3 <$Revision: 1528965 $> +Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ +Licensed to The Apache Software Foundation, http://www.apache.org/ + +Benchmarking 127.0.0.1 (be patient) +Completed 100000 requests +Completed 200000 requests +Completed 300000 requests +Completed 400000 requests +Completed 500000 requests +Completed 600000 requests +Completed 700000 requests +Completed 800000 requests +Completed 900000 requests +Completed 1000000 requests +Finished 1000000 requests + + +Server Software: workerman/3.1.4 +Server Hostname: 127.0.0.1 +Server Port: 1234 + +Document Path: / +Document Length: 5 bytes + +Concurrency Level: 100 +Time taken for tests: 7.240 seconds +Complete requests: 1000000 +Failed requests: 0 +Keep-Alive requests: 1000000 +Total transferred: 73000000 bytes +HTML transferred: 5000000 bytes +Requests per second: 138124.14 [#/sec] (mean) +Time per request: 0.724 [ms] (mean) +Time per request: 0.007 [ms] (mean, across all concurrent requests) +Transfer rate: 9846.74 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 0 0.0 0 5 +Processing: 0 1 0.2 1 9 +Waiting: 0 1 0.2 1 9 +Total: 0 1 0.2 1 9 + +Percentage of the requests served within a certain time (ms) + 50% 1 + 66% 1 + 75% 1 + 80% 1 + 90% 1 + 95% 1 + 98% 1 + 99% 1 + 100% 9 (longest request) + +``` + + +## Other links with workerman + +[PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[php-socks5](https://github.com/walkor/php-socks5) +[php-http-proxy](https://github.com/walkor/php-http-proxy) + +## Donate + + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/WebServer.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/WebServer.php new file mode 100644 index 0000000..82ea776 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/WebServer.php @@ -0,0 +1,311 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Protocols\Http; +use Workerman\Protocols\HttpCache; + +/** + * WebServer. + */ +class WebServer extends Worker +{ + /** + * Virtual host to path mapping. + * + * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] + */ + protected $serverRoot = array(); + + /** + * Mime mapping. + * + * @var array + */ + protected static $mimeTypeMap = array(); + + + /** + * Used to save user OnWorkerStart callback settings. + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * Add virtual host. + * + * @param string $domain + * @param string $config + * @return void + */ + public function addRoot($domain, $config) + { + if (is_string($config)) { + $config = array('root' => $config); + } + $this->serverRoot[$domain] = $config; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, $context_option = array()) + { + list(, $address) = explode(':', $socket_name, 2); + parent::__construct('http:' . $address, $context_option); + $this->name = 'WebServer'; + } + + /** + * Run webserver instance. + * + * @see Workerman.Worker::run() + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onMessage = array($this, 'onMessage'); + parent::run(); + } + + /** + * Emit when process start. + * + * @throws \Exception + */ + public function onWorkerStart() + { + if (empty($this->serverRoot)) { + Worker::safeEcho(new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path')); + exit(250); + } + + // Init mimeMap. + $this->initMimeTypeMap(); + + // Try to emit onWorkerStart callback. + if ($this->_onWorkerStart) { + try { + call_user_func($this->_onWorkerStart, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + } + + /** + * Init mime map. + * + * @return void + */ + public function initMimeTypeMap() + { + $mime_file = Http::getMimeTypesFile(); + if (!is_file($mime_file)) { + $this->log("$mime_file mime.type file not fond"); + return; + } + $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($items)) { + $this->log("get $mime_file mime.type content fail"); + return; + } + foreach ($items as $content) { + if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $workerman_file_extension_var = $match[2]; + $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); + foreach ($workerman_file_extension_array as $workerman_file_extension) { + self::$mimeTypeMap[$workerman_file_extension] = $mime_type; + } + } + } + } + + /** + * Emit when http message coming. + * + * @param Connection\TcpConnection $connection + * @return void + */ + public function onMessage($connection) + { + // REQUEST_URI. + $workerman_url_info = parse_url('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); + if (!$workerman_url_info) { + Http::header('HTTP/1.1 400 Bad Request'); + $connection->close('

400 Bad Request

'); + return; + } + + $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; + + $workerman_path_info = pathinfo($workerman_path); + $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; + if ($workerman_file_extension === '') { + $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; + $workerman_file_extension = 'php'; + } + + $workerman_siteConfig = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot); + $workerman_root_dir = $workerman_siteConfig['root']; + $workerman_file = "$workerman_root_dir/$workerman_path"; + if(isset($workerman_siteConfig['additionHeader'])){ + Http::header($workerman_siteConfig['additionHeader']); + } + if ($workerman_file_extension === 'php' && !is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.php"; + if (!is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.html"; + $workerman_file_extension = 'html'; + } + } + + // File exsits. + if (is_file($workerman_file)) { + // Security check. + if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath, + $workerman_root_dir_realpath) + ) { + Http::header('HTTP/1.1 400 Bad Request'); + $connection->close('

400 Bad Request

'); + return; + } + + $workerman_file = realpath($workerman_file); + + // Request php file. + if ($workerman_file_extension === 'php') { + $workerman_cwd = getcwd(); + chdir($workerman_root_dir); + ini_set('display_errors', 'off'); + ob_start(); + // Try to include php file. + try { + // $_SERVER. + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + include $workerman_file; + } catch (\Exception $e) { + // Jump_exit? + if ($e->getMessage() != 'jump_exit') { + Worker::safeEcho($e); + } + } + $content = ob_get_clean(); + ini_set('display_errors', 'on'); + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($content); + } else { + $connection->close($content); + } + chdir($workerman_cwd); + return; + } + + // Send file to client. + return self::sendFile($connection, $workerman_file); + } else { + // 404 + Http::header("HTTP/1.1 404 Not Found"); + if(isset($workerman_siteConfig['custom404']) && file_exists($workerman_siteConfig['custom404'])){ + $html404 = file_get_contents($workerman_siteConfig['custom404']); + }else{ + $html404 = '404 File not found

404 Not Found

'; + } + $connection->close($html404); + return; + } + } + + public static function sendFile($connection, $file_path) + { + // Check 304. + $info = stat($file_path); + $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : ''; + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { + // Http 304. + if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { + // 304 + Http::header('HTTP/1.1 304 Not Modified'); + // Send nothing but http headers.. + $connection->close(''); + return; + } + } + + // Http header. + if ($modified_time) { + $modified_time = "Last-Modified: $modified_time\r\n"; + } + $file_size = filesize($file_path); + $file_info = pathinfo($file_path); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; + $header = "HTTP/1.1 200 OK\r\n"; + if (isset(self::$mimeTypeMap[$extension])) { + $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; + } else { + $header .= "Content-Type: application/octet-stream\r\n"; + $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; + } + $header .= "Connection: keep-alive\r\n"; + $header .= $modified_time; + $header .= "Content-Length: $file_size\r\n\r\n"; + $trunk_limit_size = 1024*1024; + if ($file_size < $trunk_limit_size) { + return $connection->send($header.file_get_contents($file_path), true); + } + $connection->send($header, true); + + // Read file content from disk piece by piece and send to client. + $connection->fileHandler = fopen($file_path, 'r'); + $do_write = function()use($connection) + { + // Send buffer not full. + while(empty($connection->bufferFull)) + { + // Read from disk. + $buffer = fread($connection->fileHandler, 8192); + // Read eof. + if($buffer === '' || $buffer === false) + { + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function($connection) + { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function($connection)use($do_write) + { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Worker.php b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Worker.php new file mode 100644 index 0000000..401b3d4 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/Worker.php @@ -0,0 +1,2439 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Workerman\Events\Select; +use Exception; + +/** + * Worker class + * A container for listening ports + */ +class Worker +{ + /** + * Version. + * + * @var string + */ + const VERSION = '3.5.15'; + + /** + * Status starting. + * + * @var int + */ + const STATUS_STARTING = 1; + + /** + * Status running. + * + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * Status shutdown. + * + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * Status reloading. + * + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * After sending the restart command to the child process KILL_WORKER_TIMER_TIME seconds, + * if the process is still living then forced to kill. + * + * @var int + */ + const KILL_WORKER_TIMER_TIME = 2; + + /** + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * + * @var int + */ + const DEFAULT_BACKLOG = 102400; + /** + * Max udp package size. + * + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * The safe distance for columns adjacent + * + * @var int + */ + const UI_SAFE_LENGTH = 4; + + /** + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * + * @var string + */ + public $name = 'none'; + + /** + * Number of worker processes. + * + * @var int + */ + public $count = 1; + + /** + * Unix user of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $user = ''; + + /** + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * + * @var bool + */ + public $reloadable = true; + + /** + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * + * @var callback + */ + public $onWorkerStart = null; + + /** + * Emitted when a socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callback + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callback + */ + public $onBufferDrain = null; + + /** + * Emitted when worker processes stoped. + * + * @var callback + */ + public $onWorkerStop = null; + + /** + * Emitted when worker processes get reload signal. + * + * @var callback + */ + public $onWorkerReload = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Store all connections of clients. + * + * @var array + */ + public $connections = array(); + + /** + * Application layer protocol. + * + * @var string + */ + public $protocol = null; + + /** + * Root path for autoload. + * + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * Pause accept new connections or not. + * + * @var bool + */ + protected $_pauseAccept = true; + + /** + * Is worker stopping ? + * @var bool + */ + public $stopping = false; + + /** + * Daemonize. + * + * @var bool + */ + public static $daemonize = false; + + /** + * Stdout file. + * + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * The file to store master process PID. + * + * @var string + */ + public static $pidFile = ''; + + /** + * Log file. + * + * @var mixed + */ + public static $logFile = ''; + + /** + * Global event loop. + * + * @var Events\EventInterface + */ + public static $globalEvent = null; + + /** + * Emitted when the master process get reload signal. + * + * @var callback + */ + public static $onMasterReload = null; + + /** + * Emitted when the master process terminated. + * + * @var callback + */ + public static $onMasterStop = null; + + /** + * EventLoopClass + * + * @var string + */ + public static $eventLoopClass = ''; + + /** + * The PID of master process. + * + * @var int + */ + protected static $_masterPid = 0; + + /** + * Listening socket. + * + * @var resource + */ + protected $_mainSocket = null; + + /** + * Socket name. The format is like this http://0.0.0.0:80 . + * + * @var string + */ + protected $_socketName = ''; + + /** + * Context of socket. + * + * @var resource + */ + protected $_context = null; + + /** + * All worker instances. + * + * @var Worker[] + */ + protected static $_workers = array(); + + /** + * All worker porcesses pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * + * @var array + */ + protected static $_pidMap = array(); + + /** + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * Maximum length of the worker names. + * + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * Maximum length of the socket names. + * + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * Maximum length of the process user names. + * + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * Maximum length of the Proto names. + * + * @var int + */ + protected static $_maxProtoNameLength = 4; + + /** + * Maximum length of the Processes names. + * + * @var int + */ + protected static $_maxProcessesNameLength = 9; + + /** + * Maximum length of the Status names. + * + * @var int + */ + protected static $_maxStatusNameLength = 1; + + /** + * The file to store status info of current worker process. + * + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * Start file. + * + * @var string + */ + protected static $_startFile = ''; + + /** + * OS. + * + * @var string + */ + protected static $_OS = OS_TYPE_LINUX; + + /** + * Processes for windows. + * + * @var array + */ + protected static $_processForWindows = array(); + + /** + * Status info of current worker process. + * + * @var array + */ + protected static $_globalStatistics = array( + 'start_timestamp' => 0, + 'worker_exit_info' => array() + ); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'libevent' => '\Workerman\Events\Libevent', + 'event' => '\Workerman\Events\Event' + // Temporarily removed swoole because it is not stable enough + //'swoole' => '\Workerman\Events\Swoole' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * Graceful stop or not. + * + * @var string + */ + protected static $_gracefulStop = false; + + /** + * Standard output stream + * @var resource + */ + protected static $_outputStream = null; + + /** + * If $outputStream support decorated + * @var bool + */ + protected static $_outputDecorated = null; + + /** + * Run all worker instances. + * + * @return void + */ + public static function runAll() + { + static::checkSapiEnv(); + static::init(); + static::parseCommand(); + static::daemonize(); + static::initWorkers(); + static::installSignal(); + static::saveMasterPid(); + static::displayUI(); + static::forkWorkers(); + static::resetStd(); + static::monitorWorkers(); + } + + /** + * Check sapi. + * + * @return void + */ + protected static function checkSapiEnv() + { + // Only for cli. + if (php_sapi_name() != "cli") { + exit("only run in command line mode \n"); + } + if (DIRECTORY_SEPARATOR === '\\') { + self::$_OS = OS_TYPE_WINDOWS; + } + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + + // Start file. + $backtrace = debug_backtrace(); + static::$_startFile = $backtrace[count($backtrace) - 1]['file']; + + + $unique_prefix = str_replace('/', '_', static::$_startFile); + + // Pid file. + if (empty(static::$pidFile)) { + static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; + } + + // Log file. + if (empty(static::$logFile)) { + static::$logFile = __DIR__ . '/../workerman.log'; + } + $log_file = (string)static::$logFile; + if (!is_file($log_file)) { + touch($log_file); + chmod($log_file, 0622); + } + + // State. + static::$_status = static::STATUS_STARTING; + + // For statistics. + static::$_globalStatistics['start_timestamp'] = time(); + static::$_statisticsFile = sys_get_temp_dir() . "/$unique_prefix.status"; + + // Process title. + static::setProcessTitle('WorkerMan: master process start_file=' . static::$_startFile); + + // Init data for worker id. + static::initId(); + + // Timer init. + Timer::init(); + } + + /** + * Init All worker instances. + * + * @return void + */ + protected static function initWorkers() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + foreach (static::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { + $worker->name = 'none'; + } + + // Get unix user of the worker process. + if (empty($worker->user)) { + $worker->user = static::getCurrentUser(); + } else { + if (posix_getuid() !== 0 && $worker->user != static::getCurrentUser()) { + static::log('Warning: You must have the root privileges to change uid and gid.'); + } + } + + // Socket name. + $worker->socket = $worker->getSocketName(); + + // Status name. + $worker->status = ' [OK] '; + + // Get clolumn mapping for UI + foreach(static::getUiColumns() as $column_name => $prop){ + !isset($worker->{$prop}) && $worker->{$prop}= 'NNNN'; + $prop_length = strlen($worker->{$prop}); + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + static::$$key = max(static::$$key, $prop_length); + } + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } + } + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return static::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return static::$globalEvent; + } + + /** + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (static::$_workers as $worker_id => $worker) { + $new_id_map = array(); + $worker->count = $worker->count <= 0 ? 1 : $worker->count; + for($key = 0; $key < $worker->count; $key++) { + $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; + } + static::$_idMap[$worker_id] = $new_id_map; + } + } + + /** + * Get unix user of current porcess. + * + * @return string + */ + protected static function getCurrentUser() + { + $user_info = posix_getpwuid(posix_getuid()); + return $user_info['name']; + } + + /** + * Display staring UI. + * + * @return void + */ + protected static function displayUI() + { + global $argv; + if (in_array('-q', $argv)) { + return; + } + if (static::$_OS !== OS_TYPE_LINUX) { + static::safeEcho("----------------------- WORKERMAN -----------------------------\r\n"); + static::safeEcho('Workerman version:'. static::VERSION. " PHP version:". PHP_VERSION. "\r\n"); + static::safeEcho("------------------------ WORKERS -------------------------------\r\n"); + static::safeEcho("worker listen processes status\r\n"); + return; + } + + //show version + $line_version = 'Workerman version:' . static::VERSION . str_pad('PHP version:', 22, ' ', STR_PAD_LEFT) . PHP_VERSION . PHP_EOL; + !defined('LINE_VERSIOIN_LENGTH') && define('LINE_VERSIOIN_LENGTH', strlen($line_version)); + $total_length = static::getSingleLineTotalLength(); + $line_one = '' . str_pad(' WORKERMAN ', $total_length + strlen(''), '-', STR_PAD_BOTH) . ''. PHP_EOL; + $line_two = str_pad(' WORKERS ' , $total_length + strlen(''), '-', STR_PAD_BOTH) . PHP_EOL; + static::safeEcho($line_one . $line_version . $line_two); + + //Show title + $title = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + //just keep compatible with listen name + $column_name == 'socket' && $column_name = 'listen'; + $title.= "{$column_name}" . str_pad('', static::$$key + static::UI_SAFE_LENGTH - strlen($column_name)); + } + $title && static::safeEcho($title . PHP_EOL); + + //Show content + foreach (static::$_workers as $worker) { + $content = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + preg_match_all("/(|<\/n>||<\/w>||<\/g>)/is", $worker->{$prop}, $matches); + $place_holder_length = !empty($matches) ? strlen(implode('', $matches[0])) : 0; + $content .= str_pad($worker->{$prop}, static::$$key + static::UI_SAFE_LENGTH + $place_holder_length); + } + $content && static::safeEcho($content . PHP_EOL); + } + + //Show last line + $line_last = str_pad('', static::getSingleLineTotalLength(), '-') . PHP_EOL; + $content && static::safeEcho($line_last); + + if (static::$daemonize) { + static::safeEcho("Input \"php $argv[0] stop\" to stop. Start success.\n\n"); + } else { + static::safeEcho("Press Ctrl+C to stop. Start success.\n"); + } + } + + /** + * Get UI columns to be shown in terminal + * + * 1. $column_map: array('ui_column_name' => 'clas_property_name') + * 2. Consider move into configuration in future + * + * @return array + */ + public static function getUiColumns() + { + $column_map = array( + 'proto' => 'transport', + 'user' => 'user', + 'worker' => 'name', + 'socket' => 'socket', + 'processes' => 'count', + 'status' => 'status', + ); + + return $column_map; + } + + /** + * Get single line total length for ui + * + * @return int + */ + public static function getSingleLineTotalLength() + { + $total_length = 0; + + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + $total_length += static::$$key + static::UI_SAFE_LENGTH; + } + + //keep beauty when show less colums + !defined('LINE_VERSIOIN_LENGTH') && define('LINE_VERSIOIN_LENGTH', 0); + $total_length <= LINE_VERSIOIN_LENGTH && $total_length = LINE_VERSIOIN_LENGTH; + + return $total_length; + } + + /** + * Parse command. + * + * @return void + */ + protected static function parseCommand() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + global $argv; + // Check argv; + $start_file = $argv[0]; + $available_commands = array( + 'start', + 'stop', + 'restart', + 'reload', + 'status', + 'connections', + ); + $usage = "Usage: php yourfile [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; + if (!isset($argv[1]) || !in_array($argv[1], $available_commands)) { + if (isset($argv[1])) { + static::safeEcho('Unknown command: ' . $argv[1] . "\n"); + } + exit($usage); + } + + // Get command. + $command = trim($argv[1]); + $command2 = isset($argv[2]) ? $argv[2] : ''; + + // Start command. + $mode = ''; + if ($command === 'start') { + if ($command2 === '-d' || static::$daemonize) { + $mode = 'in DAEMON mode'; + } else { + $mode = 'in DEBUG mode'; + } + } + static::log("Workerman[$start_file] $command $mode"); + + // Get master process PID. + $master_pid = is_file(static::$pidFile) ? file_get_contents(static::$pidFile) : 0; + $master_is_alive = $master_pid && posix_kill($master_pid, 0) && posix_getpid() != $master_pid; + // Master is still alive? + if ($master_is_alive) { + if ($command === 'start') { + static::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart') { + static::log("Workerman[$start_file] not run"); + exit; + } + + // execute command. + switch ($command) { + case 'start': + if ($command2 === '-d') { + static::$daemonize = true; + } + break; + case 'status': + while (1) { + if (is_file(static::$_statisticsFile)) { + @unlink(static::$_statisticsFile); + } + // Master process will send SIGUSR2 signal to all child processes. + posix_kill($master_pid, SIGUSR2); + // Sleep 1 second. + sleep(1); + // Clear terminal. + if ($command2 === '-d') { + static::safeEcho("\33[H\33[2J\33(B\33[m", true); + } + // Echo status data. + static::safeEcho(static::formatStatusData()); + if ($command2 !== '-d') { + exit(0); + } + static::safeEcho("\nPress Ctrl+C to quit.\n\n"); + } + exit(0); + case 'connections': + if (is_file(static::$_statisticsFile) && is_writable(static::$_statisticsFile)) { + unlink(static::$_statisticsFile); + } + // Master process will send SIGIO signal to all child processes. + posix_kill($master_pid, SIGIO); + // Waiting amoment. + usleep(500000); + // Display statisitcs data from a disk file. + if(is_readable(static::$_statisticsFile)) { + readfile(static::$_statisticsFile); + } + exit(0); + case 'restart': + case 'stop': + if ($command2 === '-g') { + static::$_gracefulStop = true; + $sig = SIGTERM; + static::log("Workerman[$start_file] is gracefully stopping ..."); + } else { + static::$_gracefulStop = false; + $sig = SIGINT; + static::log("Workerman[$start_file] is stopping ..."); + } + // Send stop signal to master process. + $master_pid && posix_kill($master_pid, $sig); + // Timeout. + $timeout = 5; + $start_time = time(); + // Check master process is still alive? + while (1) { + $master_is_alive = $master_pid && posix_kill($master_pid, 0); + if ($master_is_alive) { + // Timeout? + if (!static::$_gracefulStop && time() - $start_time >= $timeout) { + static::log("Workerman[$start_file] stop fail"); + exit; + } + // Waiting amoment. + usleep(10000); + continue; + } + // Stop success. + static::log("Workerman[$start_file] stop success"); + if ($command === 'stop') { + exit(0); + } + if ($command2 === '-d') { + static::$daemonize = true; + } + break; + } + break; + case 'reload': + if($command2 === '-g'){ + $sig = SIGQUIT; + }else{ + $sig = SIGUSR1; + } + posix_kill($master_pid, $sig); + exit; + default : + if (isset($command)) { + static::safeEcho('Unknown command: ' . $command . "\n"); + } + exit($usage); + } + } + + /** + * Format status data. + * + * @return string + */ + protected static function formatStatusData() + { + static $total_request_cache = array(); + if (!is_readable(static::$_statisticsFile)) { + return ''; + } + $info = file(static::$_statisticsFile, FILE_IGNORE_NEW_LINES); + if (!$info) { + return ''; + } + $status_str = ''; + $current_total_request = array(); + $worker_info = json_decode($info[0], true); + ksort($worker_info, SORT_NUMERIC); + unset($info[0]); + $data_waiting_sort = array(); + $read_process_status = false; + $total_requests = 0; + $total_qps = 0; + $total_connections = 0; + $total_fails = 0; + $total_memory = 0; + $total_timers = 0; + $maxLen1 = static::$_maxSocketNameLength; + $maxLen2 = static::$_maxWorkerNameLength; + foreach($info as $key => $value) { + if (!$read_process_status) { + $status_str .= $value . "\n"; + if (preg_match('/^pid.*?memory.*?listening/', $value)) { + $read_process_status = true; + } + continue; + } + if(preg_match('/^[0-9]+/', $value, $pid_math)) { + $pid = $pid_math[0]; + $data_waiting_sort[$pid] = $value; + if(preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) { + $total_memory += intval(str_ireplace('M','',$match[1])); + $maxLen1 = max($maxLen1,strlen($match[2])); + $maxLen2 = max($maxLen2,strlen($match[3])); + $total_connections += intval($match[4]); + $total_fails += intval($match[5]); + $total_timers += intval($match[6]); + $current_total_request[$pid] = $match[7]; + $total_requests += intval($match[7]); + } + } + } + foreach($worker_info as $pid => $info) { + if (!isset($data_waiting_sort[$pid])) { + $status_str .= "$pid\t" . str_pad('N/A', 7) . " " + . str_pad($info['listen'], static::$_maxSocketNameLength) . " " + . str_pad($info['name'], static::$_maxWorkerNameLength) . " " + . str_pad('N/A', 11) . " " . str_pad('N/A', 9) . " " + . str_pad('N/A', 7) . " " . str_pad('N/A', 13) . " N/A [busy] \n"; + continue; + } + //$qps = isset($total_request_cache[$pid]) ? $current_total_request[$pid] + if (!isset($total_request_cache[$pid]) || !isset($current_total_request[$pid])) { + $qps = 0; + } else { + $qps = $current_total_request[$pid] - $total_request_cache[$pid]; + $total_qps += $qps; + } + $status_str .= $data_waiting_sort[$pid]. " " . str_pad($qps, 6) ." [idle]\n"; + } + $total_request_cache = $current_total_request; + $status_str .= "----------------------------------------------PROCESS STATUS---------------------------------------------------\n"; + $status_str .= "Summary\t" . str_pad($total_memory.'M', 7) . " " + . str_pad('-', $maxLen1) . " " + . str_pad('-', $maxLen2) . " " + . str_pad($total_connections, 11) . " " . str_pad($total_fails, 9) . " " + . str_pad($total_timers, 7) . " " . str_pad($total_requests, 13) . " " + . str_pad($total_qps,6)." [Summary] \n"; + return $status_str; + } + + + /** + * Install signal handler. + * + * @return void + */ + protected static function installSignal() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + // stop + pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false); + // graceful stop + pcntl_signal(SIGTERM, array('\Workerman\Worker', 'signalHandler'), false); + // reload + pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false); + // graceful reload + pcntl_signal(SIGQUIT, array('\Workerman\Worker', 'signalHandler'), false); + // status + pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false); + // connection status + pcntl_signal(SIGIO, array('\Workerman\Worker', 'signalHandler'), false); + // ignore + pcntl_signal(SIGPIPE, SIG_IGN, false); + } + + /** + * Reinstall signal handler. + * + * @return void + */ + protected static function reinstallSignal() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + // uninstall stop signal handler + pcntl_signal(SIGINT, SIG_IGN, false); + // uninstall graceful stop signal handler + pcntl_signal(SIGTERM, SIG_IGN, false); + // uninstall reload signal handler + pcntl_signal(SIGUSR1, SIG_IGN, false); + // uninstall graceful reload signal handler + pcntl_signal(SIGQUIT, SIG_IGN, false); + // uninstall status signal handler + pcntl_signal(SIGUSR2, SIG_IGN, false); + // reinstall stop signal handler + static::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall graceful stop signal handler + static::$globalEvent->add(SIGTERM, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall reload signal handler + static::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall graceful reload signal handler + static::$globalEvent->add(SIGQUIT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall status signal handler + static::$globalEvent->add(SIGUSR2, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall connection status signal handler + static::$globalEvent->add(SIGIO, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + } + + /** + * Signal handler. + * + * @param int $signal + */ + public static function signalHandler($signal) + { + switch ($signal) { + // Stop. + case SIGINT: + static::$_gracefulStop = false; + static::stopAll(); + break; + // Graceful stop. + case SIGTERM: + static::$_gracefulStop = true; + static::stopAll(); + break; + // Reload. + case SIGQUIT: + case SIGUSR1: + if($signal === SIGQUIT){ + static::$_gracefulStop = true; + }else{ + static::$_gracefulStop = false; + } + static::$_pidsToRestart = static::getAllWorkerPids(); + static::reload(); + break; + // Show status. + case SIGUSR2: + static::writeStatisticsToStatusFile(); + break; + // Show connection status. + case SIGIO: + static::writeConnectionsStatisticsToStatusFile(); + break; + } + } + + /** + * Run as deamon mode. + * + * @throws Exception + */ + protected static function daemonize() + { + if (!static::$daemonize || static::$_OS !== OS_TYPE_LINUX) { + return; + } + umask(0); + $pid = pcntl_fork(); + if (-1 === $pid) { + throw new Exception('fork fail'); + } elseif ($pid > 0) { + exit(0); + } + if (-1 === posix_setsid()) { + throw new Exception("setsid fail"); + } + // Fork again avoid SVR4 system regain the control of terminal. + $pid = pcntl_fork(); + if (-1 === $pid) { + throw new Exception("fork fail"); + } elseif (0 !== $pid) { + exit(0); + } + } + + /** + * Redirect standard input and output. + * + * @throws Exception + */ + public static function resetStd() + { + if (!static::$daemonize || static::$_OS !== OS_TYPE_LINUX) { + return; + } + global $STDOUT, $STDERR; + $handle = fopen(static::$stdoutFile, "a"); + if ($handle) { + unset($handle); + set_error_handler(function(){}); + fclose($STDOUT); + fclose($STDERR); + fclose(STDOUT); + fclose(STDERR); + $STDOUT = fopen(static::$stdoutFile, "a"); + $STDERR = fopen(static::$stdoutFile, "a"); + // change output stream + static::$_outputStream = null; + static::outputStream($STDOUT); + restore_error_handler(); + } else { + throw new Exception('can not open stdoutFile ' . static::$stdoutFile); + } + } + + /** + * Save pid. + * + * @throws Exception + */ + protected static function saveMasterPid() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + static::$_masterPid = posix_getpid(); + if (false === file_put_contents(static::$pidFile, static::$_masterPid)) { + throw new Exception('can not save pid to ' . static::$pidFile); + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (static::$eventLoopClass) { + return static::$eventLoopClass; + } + + if (!class_exists('\Swoole\Event', false)) { + unset(static::$_availableEventLoops['swoole']); + } + + $loop_name = ''; + foreach (static::$_availableEventLoops as $name=>$class) { + if (extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + if (interface_exists('\React\EventLoop\LoopInterface')) { + switch ($loop_name) { + case 'libevent': + static::$eventLoopClass = '\Workerman\Events\React\ExtLibEventLoop'; + break; + case 'event': + static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop'; + break; + default : + static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop'; + break; + } + } else { + static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; + } + } else { + static::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select'; + } + return static::$eventLoopClass; + } + + /** + * Get all pids of worker processes. + * + * @return array + */ + protected static function getAllWorkerPids() + { + $pid_array = array(); + foreach (static::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { + $pid_array[$worker_pid] = $worker_pid; + } + } + return $pid_array; + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkers() + { + if (static::$_OS === OS_TYPE_LINUX) { + static::forkWorkersForLinux(); + } else { + static::forkWorkersForWindows(); + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForLinux() + { + + foreach (static::$_workers as $worker) { + if (static::$_status === static::STATUS_STARTING) { + if (empty($worker->name)) { + $worker->name = $worker->getSocketName(); + } + $worker_name_length = strlen($worker->name); + if (static::$_maxWorkerNameLength < $worker_name_length) { + static::$_maxWorkerNameLength = $worker_name_length; + } + } + + while (count(static::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorkerForLinux($worker); + } + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForWindows() + { + $files = static::getStartFilesForWindows(); + global $argv; + if(in_array('-q', $argv) || count($files) === 1) + { + if(count(static::$_workers) > 1) + { + static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n"); + static::safeEcho("@@@ Please visit http://wiki.workerman.net/Multi_woker_for_win @@@\r\n"); + } + elseif(count(static::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + reset(static::$_workers); + /** @var Worker $worker */ + $worker = current(static::$_workers); + + // Display UI. + static::safeEcho(str_pad($worker->name, 21) . str_pad($worker->getSocketName(), 36) . str_pad($worker->count, 10) . "[ok]\n"); + $worker->listen(); + $worker->run(); + exit("@@@child exit@@@\r\n"); + } + else + { + static::$globalEvent = new \Workerman\Events\Select(); + Timer::init(static::$globalEvent); + foreach($files as $start_file) + { + static::forkOneWorkerForWindows($start_file); + } + } + } + + /** + * Get start files for windows. + * + * @return array + */ + public static function getStartFilesForWindows() { + global $argv; + $files = array(); + foreach($argv as $file) + { + if(is_file($file)) + { + $files[$file] = $file; + } + } + return $files; + } + + /** + * Fork one worker process. + * + * @param string $start_file + */ + public static function forkOneWorkerForWindows($start_file) + { + $start_file = realpath($start_file); + $std_file = sys_get_temp_dir() . '/'.str_replace(array('/', "\\", ':'), '_', $start_file).'.out.txt'; + + $descriptorspec = array( + 0 => array('pipe', 'a'), // stdin + 1 => array('file', $std_file, 'w'), // stdout + 2 => array('file', $std_file, 'w') // stderr + ); + + + $pipes = array(); + $process = proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + $std_handler = fopen($std_file, 'a+'); + stream_set_blocking($std_handler, 0); + + if (empty(static::$globalEvent)) { + static::$globalEvent = new Select(); + Timer::init(static::$globalEvent); + } + $timer_id = Timer::add(0.1, function()use($std_handler) + { + Worker::safeEcho(fread($std_handler, 65535)); + }); + + // 保存子进程句柄 + static::$_processForWindows[$start_file] = array($process, $start_file, $timer_id); + } + + /** + * check worker status for windows. + * @return void + */ + public static function checkWorkerStatusForWindows() + { + foreach(static::$_processForWindows as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $timer_id = $process_data[2]; + $status = proc_get_status($process); + if(isset($status['running'])) + { + if(!$status['running']) + { + static::safeEcho("process $start_file terminated and try to restart\n"); + Timer::del($timer_id); + proc_close($process); + static::forkOneWorkerForWindows($start_file); + } + } + else + { + static::safeEcho("proc_get_status fail\n"); + } + } + } + + + /** + * Fork one worker process. + * + * @param \Workerman\Worker $worker + * @throws Exception + */ + protected static function forkOneWorkerForLinux($worker) + { + // Get available worker id. + $id = static::getId($worker->workerId, 0); + if ($id === false) { + return; + } + $pid = pcntl_fork(); + // For master process. + if ($pid > 0) { + static::$_pidMap[$worker->workerId][$pid] = $pid; + static::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + srand(); + mt_srand(); + if ($worker->reusePort) { + $worker->listen(); + } + if (static::$_status === static::STATUS_STARTING) { + static::resetStd(); + } + static::$_pidMap = array(); + // Remove other listener. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId !== $worker->workerId) { + $one_worker->unlisten(); + unset(static::$_workers[$key]); + } + } + Timer::delAll(); + static::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName()); + $worker->setUserAndGroup(); + $worker->id = $id; + $worker->run(); + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } else { + throw new Exception("forkOneWorker fail"); + } + } + + /** + * Get worker id. + * + * @param int $worker_id + * @param int $pid + * + * @return integer + */ + protected static function getId($worker_id, $pid) + { + return array_search($pid, static::$_idMap[$worker_id]); + } + + /** + * Set unix user and group for current process. + * + * @return void + */ + public function setUserAndGroup() + { + // Get uid. + $user_info = posix_getpwnam($this->user); + if (!$user_info) { + static::log("Warning: User {$this->user} not exsits"); + return; + } + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = posix_getgrnam($this->group); + if (!$group_info) { + static::log("Warning: Group {$this->group} not exsits"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid != posix_getuid() || $gid != posix_getgid()) { + if (!posix_setgid($gid) || !posix_initgroups($user_info['name'], $gid) || !posix_setuid($uid)) { + static::log("Warning: change gid or uid fail."); + } + } + } + + /** + * Set process name. + * + * @param string $title + * @return void + */ + protected static function setProcessTitle($title) + { + set_error_handler(function(){}); + // >=php 5.5 + if (function_exists('cli_set_process_title')) { + cli_set_process_title($title); + } // Need proctitle when php<=5.5 . + elseif (extension_loaded('proctitle') && function_exists('setproctitle')) { + setproctitle($title); + } + restore_error_handler(); + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkers() + { + if (static::$_OS === OS_TYPE_LINUX) { + static::monitorWorkersForLinux(); + } else { + static::monitorWorkersForWindows(); + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForLinux() + { + static::$_status = static::STATUS_RUNNING; + while (1) { + // Calls signal handlers for pending signals. + pcntl_signal_dispatch(); + // Suspends execution of the current process until a child has exited, or until a signal is delivered + $status = 0; + $pid = pcntl_wait($status, WUNTRACED); + // Calls signal handlers for pending signals again. + pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out witch worker process exited. + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { + $worker = static::$_workers[$worker_id]; + // Exit status. + if ($status !== 0) { + static::log("worker[" . $worker->name . ":$pid] exit with status $status"); + } + + // For Statistics. + if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { + static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; + } + static::$_globalStatistics['worker_exit_info'][$worker_id][$status]++; + + // Clear process data. + unset(static::$_pidMap[$worker_id][$pid]); + + // Mark id is available. + $id = static::getId($worker_id, $pid); + static::$_idMap[$worker_id][$id] = 0; + + break; + } + } + // Is still running state then fork a new worker process. + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::forkWorkers(); + // If reloading continue. + if (isset(static::$_pidsToRestart[$pid])) { + unset(static::$_pidsToRestart[$pid]); + static::reload(); + } + } + } + + // If shutdown state and all child processes exited then master process exit. + if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { + static::exitAndClearAll(); + } + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForWindows() + { + Timer::add(1, "\\Workerman\\Worker::checkWorkerStatusForWindows"); + + static::$globalEvent->loop(); + } + + /** + * Exit current process. + * + * @return void + */ + protected static function exitAndClearAll() + { + foreach (static::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = explode(':', $socket_name, 2); + @unlink($address); + } + } + @unlink(static::$pidFile); + static::log("Workerman[" . basename(static::$_startFile) . "] has been stopped"); + if (static::$onMasterStop) { + call_user_func(static::$onMasterStop); + } + exit(0); + } + + /** + * Execute reload. + * + * @return void + */ + protected static function reload() + { + // For master process. + if (static::$_masterPid === posix_getpid()) { + // Set reloading state. + if (static::$_status !== static::STATUS_RELOADING && static::$_status !== static::STATUS_SHUTDOWN) { + static::log("Workerman[" . basename(static::$_startFile) . "] reloading"); + static::$_status = static::STATUS_RELOADING; + // Try to emit onMasterReload callback. + if (static::$onMasterReload) { + try { + call_user_func(static::$onMasterReload); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + static::initId(); + } + } + + if (static::$_gracefulStop) { + $sig = SIGQUIT; + } else { + $sig = SIGUSR1; + } + + // Send reload signal to all child processes. + $reloadable_pid_array = array(); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { + $reloadable_pid_array[$pid] = $pid; + } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + posix_kill($pid, $sig); + } + } + } + + // Get all pids that are waiting reload. + static::$_pidsToRestart = array_intersect(static::$_pidsToRestart, $reloadable_pid_array); + + // Reload complete. + if (empty(static::$_pidsToRestart)) { + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::$_status = static::STATUS_RUNNING; + } + return; + } + // Continue reload. + $one_worker_pid = current(static::$_pidsToRestart); + // Send reload signal to a worker process. + posix_kill($one_worker_pid, $sig); + // If the process does not exit after static::KILL_WORKER_TIMER_TIME seconds try to kill it. + if(!static::$_gracefulStop){ + Timer::add(static::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false); + } + } // For child processes. + else { + reset(static::$_workers); + $worker = current(static::$_workers); + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + + if ($worker->reloadable) { + static::stopAll(); + } + } + } + + /** + * Stop. + * + * @return void + */ + public static function stopAll() + { + static::$_status = static::STATUS_SHUTDOWN; + // For master process. + if (static::$_masterPid === posix_getpid()) { + static::log("Workerman[" . basename(static::$_startFile) . "] stopping ..."); + $worker_pid_array = static::getAllWorkerPids(); + // Send stop signal to all child processes. + if (static::$_gracefulStop) { + $sig = SIGTERM; + } else { + $sig = SIGINT; + } + foreach ($worker_pid_array as $worker_pid) { + posix_kill($worker_pid, $sig); + if(!static::$_gracefulStop){ + Timer::add(static::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL), false); + } + } + Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning"); + // Remove statistics file. + if (is_file(static::$_statisticsFile)) { + @unlink(static::$_statisticsFile); + } + } // For child processes. + else { + // Execute exit. + foreach (static::$_workers as $worker) { + if(!$worker->stopping){ + $worker->stop(); + $worker->stopping = true; + } + } + if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) { + static::$_workers = array(); + if (static::$globalEvent) { + static::$globalEvent->destroy(); + } + exit(0); + } + } + } + + /** + * check if child processes is really running + */ + public static function checkIfChildRunning() + { + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + foreach ($worker_pid_array as $pid => $worker_pid) { + if (!posix_kill($pid, 0)) { + unset(static::$_pidMap[$worker_id][$pid]); + } + } + } + } + + /** + * Get process status. + * + * @return number + */ + public static function getStatus() + { + return static::$_status; + } + + /** + * If stop gracefully. + * + * @return boolean + */ + public static function getGracefulStop() + { + return static::$_gracefulStop; + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === posix_getpid()) { + $all_worker_info = array(); + foreach(static::$_pidMap as $worker_id => $pid_array) { + /** @var /Workerman/Worker $worker */ + $worker = static::$_workers[$worker_id]; + foreach($pid_array as $pid) { + $all_worker_info[$pid] = array('name' => $worker->name, 'listen' => $worker->getSocketName()); + } + } + + file_put_contents(static::$_statisticsFile, json_encode($all_worker_info)."\n", FILE_APPEND); + $loadavg = function_exists('sys_getloadavg') ? array_map('round', sys_getloadavg(), array(2)) : array('-', '-', '-'); + file_put_contents(static::$_statisticsFile, + "----------------------------------------------GLOBAL STATUS----------------------------------------------------\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, + 'Workerman version:' . static::VERSION . " PHP version:" . PHP_VERSION . "\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, 'start time:' . date('Y-m-d H:i:s', + static::$_globalStatistics['start_timestamp']) . ' run ' . floor((time() - static::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . floor(((time() - static::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . implode(", ", $loadavg); + file_put_contents(static::$_statisticsFile, + str_pad($load_str, 33) . 'event-loop:' . static::getEventLoopName() . "\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, + count(static::$_pidMap) . ' workers ' . count(static::getAllWorkerPids()) . " processes\n", + FILE_APPEND); + file_put_contents(static::$_statisticsFile, + str_pad('worker_name', static::$_maxWorkerNameLength) . " exit_status exit_count\n", FILE_APPEND); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if (isset(static::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (static::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + file_put_contents(static::$_statisticsFile, + str_pad($worker->name, static::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", FILE_APPEND); + } + } else { + file_put_contents(static::$_statisticsFile, + str_pad($worker->name, static::$_maxWorkerNameLength) . " " . str_pad(0, 16) . " 0\n", + FILE_APPEND); + } + } + file_put_contents(static::$_statisticsFile, + "----------------------------------------------PROCESS STATUS---------------------------------------------------\n", + FILE_APPEND); + file_put_contents(static::$_statisticsFile, + "pid\tmemory " . str_pad('listening', static::$_maxSocketNameLength) . " " . str_pad('worker_name', + static::$_maxWorkerNameLength) . " connections " . str_pad('send_fail', 9) . " " + . str_pad('timers', 8) . str_pad('total_request', 13) ." qps status\n", FILE_APPEND); + + chmod(static::$_statisticsFile, 0722); + + foreach (static::getAllWorkerPids() as $worker_pid) { + posix_kill($worker_pid, SIGUSR2); + } + return; + } + + // For child processes. + reset(static::$_workers); + /** @var \Workerman\Worker $worker */ + $worker = current(static::$_workers); + $worker_status_str = posix_getpid() . "\t" . str_pad(round(memory_get_usage(true) / (1024 * 1024), 2) . "M", 7) + . " " . str_pad($worker->getSocketName(), static::$_maxSocketNameLength) . " " + . str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::$_maxWorkerNameLength) + . " "; + $worker_status_str .= str_pad(ConnectionInterface::$statistics['connection_count'], 11) + . " " . str_pad(ConnectionInterface::$statistics['send_fail'], 9) + . " " . str_pad(static::$globalEvent->getTimerCount(), 7) + . " " . str_pad(ConnectionInterface::$statistics['total_request'], 13) . "\n"; + file_put_contents(static::$_statisticsFile, $worker_status_str, FILE_APPEND); + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeConnectionsStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === posix_getpid()) { + file_put_contents(static::$_statisticsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, "PID Worker CID Trans Protocol ipv4 ipv6 Recv-Q Send-Q Bytes-R Bytes-W Status Local Address Foreign Address\n", FILE_APPEND); + chmod(static::$_statisticsFile, 0722); + foreach (static::getAllWorkerPids() as $worker_pid) { + posix_kill($worker_pid, SIGIO); + } + return; + } + + // For child processes. + $bytes_format = function($bytes) + { + if($bytes > 1024*1024*1024*1024) { + return round($bytes/(1024*1024*1024*1024), 1)."TB"; + } + if($bytes > 1024*1024*1024) { + return round($bytes/(1024*1024*1024), 1)."GB"; + } + if($bytes > 1024*1024) { + return round($bytes/(1024*1024), 1)."MB"; + } + if($bytes > 1024) { + return round($bytes/(1024), 1)."KB"; + } + return $bytes."B"; + }; + + $pid = posix_getpid(); + $str = ''; + reset(static::$_workers); + $current_worker = current(static::$_workers); + $default_worker_name = $current_worker->name; + + /** @var \Workerman\Worker $worker */ + foreach(TcpConnection::$connections as $connection) { + /** @var \Workerman\Connection\TcpConnection $connection */ + $transport = $connection->transport; + $ipv4 = $connection->isIpV4() ? ' 1' : ' 0'; + $ipv6 = $connection->isIpV6() ? ' 1' : ' 0'; + $recv_q = $bytes_format($connection->getRecvBufferQueueSize()); + $send_q = $bytes_format($connection->getSendBufferQueueSize()); + $local_address = trim($connection->getLocalAddress()); + $remote_address = trim($connection->getRemoteAddress()); + $state = $connection->getStatus(false); + $bytes_read = $bytes_format($connection->bytesRead); + $bytes_written = $bytes_format($connection->bytesWritten); + $id = $connection->id; + $protocol = $connection->protocol ? $connection->protocol : $connection->transport; + $pos = strrpos($protocol, '\\'); + if ($pos) { + $protocol = substr($protocol, $pos+1); + } + if (strlen($protocol) > 15) { + $protocol = substr($protocol, 0, 13) . '..'; + } + $worker_name = isset($connection->worker) ? $connection->worker->name : $default_worker_name; + if (strlen($worker_name) > 14) { + $worker_name = substr($worker_name, 0, 12) . '..'; + } + $str .= str_pad($pid, 9) . str_pad($worker_name, 16) . str_pad($id, 10) . str_pad($transport, 8) + . str_pad($protocol, 16) . str_pad($ipv4, 7) . str_pad($ipv6, 7) . str_pad($recv_q, 13) + . str_pad($send_q, 13) . str_pad($bytes_read, 13) . str_pad($bytes_written, 13) . ' ' + . str_pad($state, 14) . ' ' . str_pad($local_address, 22) . ' ' . str_pad($remote_address, 22) ."\n"; + } + if ($str) { + file_put_contents(static::$_statisticsFile, $str, FILE_APPEND); + } + } + + /** + * Check errors when current process exited. + * + * @return void + */ + public static function checkErrors() + { + if (static::STATUS_SHUTDOWN != static::$_status) { + $error_msg = static::$_OS === OS_TYPE_LINUX ? 'Worker['. posix_getpid() .'] process terminated' : 'Worker process terminated'; + $errors = error_get_last(); + if ($errors && ($errors['type'] === E_ERROR || + $errors['type'] === E_PARSE || + $errors['type'] === E_CORE_ERROR || + $errors['type'] === E_COMPILE_ERROR || + $errors['type'] === E_RECOVERABLE_ERROR) + ) { + $error_msg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\""; + } + static::log($error_msg); + } + } + + /** + * Get error message by error code. + * + * @param integer $type + * @return string + */ + protected static function getErrorType($type) + { + switch ($type) { + case E_ERROR: // 1 // + return 'E_ERROR'; + case E_WARNING: // 2 // + return 'E_WARNING'; + case E_PARSE: // 4 // + return 'E_PARSE'; + case E_NOTICE: // 8 // + return 'E_NOTICE'; + case E_CORE_ERROR: // 16 // + return 'E_CORE_ERROR'; + case E_CORE_WARNING: // 32 // + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: // 64 // + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: // 128 // + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: // 256 // + return 'E_USER_ERROR'; + case E_USER_WARNING: // 512 // + return 'E_USER_WARNING'; + case E_USER_NOTICE: // 1024 // + return 'E_USER_NOTICE'; + case E_STRICT: // 2048 // + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: // 4096 // + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: // 8192 // + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: // 16384 // + return 'E_USER_DEPRECATED'; + } + return ""; + } + + /** + * Log. + * + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg . "\n"; + if (!static::$daemonize) { + static::safeEcho($msg); + } + file_put_contents((string)static::$logFile, date('Y-m-d H:i:s') . ' ' . 'pid:' + . (static::$_OS === OS_TYPE_LINUX ? posix_getpid() : 1) . ' ' . $msg, FILE_APPEND | LOCK_EX); + } + + /** + * Safe Echo. + * @param $msg + * @param bool $decorated + * @return bool + */ + public static function safeEcho($msg, $decorated = false) + { + $stream = static::outputStream(); + if (!$stream) { + return false; + } + if (!$decorated) { + $line = $white = $green = $end = ''; + if (static::$_outputDecorated) { + $line = "\033[1A\n\033[K"; + $white = "\033[47;30m"; + $green = "\033[32;40m"; + $end = "\033[0m"; + } + $msg = str_replace(array('', '', ''), array($line, $white, $green), $msg); + $msg = str_replace(array('', '', ''), $end, $msg); + } elseif (!static::$_outputDecorated) { + return false; + } + fwrite($stream, $msg); + fflush($stream); + return true; + } + + /** + * @param null $stream + * @return bool|resource + */ + private static function outputStream($stream = null) + { + if (!$stream) { + $stream = static::$_outputStream ? static::$_outputStream : STDOUT; + } + if (!$stream || !is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + $stat = fstat($stream); + if (($stat['mode'] & 0170000) === 0100000) { + // file + static::$_outputDecorated = false; + } else { + static::$_outputDecorated = + static::$_OS === OS_TYPE_LINUX && + function_exists('posix_isatty') && + posix_isatty($stream); + } + return static::$_outputStream = $stream; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', $context_option = array()) + { + // Save all worker instances. + $this->workerId = spl_object_hash($this); + static::$_workers[$this->workerId] = $this; + static::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backtrace[0]['file']); + + // Context for socket. + if ($socket_name) { + $this->_socketName = $socket_name; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; + } + $this->_context = stream_context_create($context_option); + } + } + + + /** + * Listen. + * + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + if (!$this->_mainSocket) { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(static::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = substr($scheme,0,1)==='\\' ? $scheme : '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + + if (!isset(static::$_builtinTransports[$this->transport])) { + throw new \Exception('Bad worker->transport ' . var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + + $local_socket = static::$_builtinTransports[$this->transport] . ":" . $address; + + // Flag. + $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + stream_socket_enable_crypto($this->_mainSocket, false); + } elseif ($this->transport === 'unix') { + $socketFile = substr($address, 2); + if ($this->user) { + chown($socketFile, $this->user); + } + if ($this->group) { + chgrp($socketFile, $this->group); + } + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { + set_error_handler(function(){}); + $socket = socket_import_stream($this->_mainSocket); + socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); + restore_error_handler(); + } + + // Non blocking. + stream_set_blocking($this->_mainSocket, 0); + } + + $this->resumeAccept(); + } + + /** + * Unlisten. + * + * @return void + */ + public function unlisten() { + $this->pauseAccept(); + if ($this->_mainSocket) { + set_error_handler(function(){}); + fclose($this->_mainSocket); + restore_error_handler(); + $this->_mainSocket = null; + } + } + + /** + * Pause accept new connections. + * + * @return void + */ + public function pauseAccept() + { + if (static::$globalEvent && false === $this->_pauseAccept && $this->_mainSocket) { + static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + $this->_pauseAccept = true; + } + } + + /** + * Resume accept new connections. + * + * @return void + */ + public function resumeAccept() + { + // Register a listener to be notified when server socket is ready to read. + if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { + if ($this->transport !== 'udp') { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, + array($this, 'acceptUdpConnection')); + } + $this->_pauseAccept = false; + } + } + + /** + * Get socket name. + * + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? lcfirst($this->_socketName) : 'none'; + } + + /** + * Run worker instance. + * + * @return void + */ + public function run() + { + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); + + // Set autoload root path. + Autoloader::setRootPath($this->_autoloadRootPath); + + // Create a global event loop. + if (!static::$globalEvent) { + $event_loop_class = static::getEventLoopName(); + static::$globalEvent = new $event_loop_class; + $this->resumeAccept(); + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + // Set an empty onMessage callback. + if (empty($this->onMessage)) { + $this->onMessage = function () {}; + } + + restore_error_handler(); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + static::log($e); + // Avoid rapid infinite loop exit. + sleep(1); + exit(250); + } catch (\Error $e) { + static::log($e); + // Avoid rapid infinite loop exit. + sleep(1); + exit(250); + } + } + + // Main loop. + static::$globalEvent->loop(); + } + + /** + * Stop current worker instance. + * + * @return void + */ + public function stop() + { + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + // Remove listener for server socket. + $this->unlisten(); + // Close all connections for the worker. + if (!static::$_gracefulStop) { + foreach ($this->connections as $connection) { + $connection->close(); + } + } + // Clear callback. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null; + } + + /** + * Accept a connection. + * + * @param resource $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + set_error_handler(function(){}); + $new_socket = stream_socket_accept($socket, 0, $remote_address); + restore_error_handler(); + + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function acceptUdpConnection($socket) + { + set_error_handler(function(){}); + $recv_buffer = stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + restore_error_handler(); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + try { + if ($this->protocol !== null) { + /** @var \Workerman\Protocols\ProtocolInterface $parser */ + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $connection); + // Discard bad packets. + if ($recv_buffer === false) + return true; + } + ConnectionInterface::$statistics['total_request']++; + call_user_func($this->onMessage, $connection, $recv_buffer); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + return true; + } +} diff --git a/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/composer.json b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/composer.json new file mode 100644 index 0000000..95c3097 --- /dev/null +++ b/application/common/Plugins/GateWayWorker/vendor/workerman/workerman/composer.json @@ -0,0 +1,38 @@ +{ + "name": "workerman/workerman", + "type": "library", + "keywords": [ + "event-loop", + "asynchronous" + ], + "homepage": "http://www.workerman.net", + "license": "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum": "http://wenda.workerman.net/", + "wiki": "http://doc3.workerman.net/index.html", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "minimum-stability": "dev" +} diff --git a/application/common/exception/Api.php b/application/common/exception/Api.php new file mode 100644 index 0000000..1c1b42b --- /dev/null +++ b/application/common/exception/Api.php @@ -0,0 +1,32 @@ +getMessage(),[],$e->getCode()); + + // 参数验证错误 + if ($e instanceof ValidateException) { + ToolsService::error($e->getError(), $e->getError(), $e->getCode()); + return json($e->getError(), 422); + } + + // 请求异常 + if ($e instanceof HttpException && request()->isAjax()) { + return response($e->getMessage(), $e->getStatusCode()); + } + var_dump($e);die; + + // 其他错误交给系统处理 + return parent::render($e); + } + +} diff --git a/application/index/controller/Index.php b/application/index/controller/Index.php new file mode 100644 index 0000000..799e598 --- /dev/null +++ b/application/index/controller/Index.php @@ -0,0 +1,201 @@ +success('success'); + + } + + /** + * 安装 + */ + public function install() + { + + $dataPath = env('root_path') . 'data/'; + //数据库配置文件 + $dbConfigFile = env('config_path') . 'database.php'; + // 锁定的文件 + $lockFile = $dataPath . 'install.lock'; + $err = ''; + + if (is_file($lockFile)) { + $err = "当前已经安装{$this->siteName},如果需要重新安装,请手动移除data/install.lock文件"; + } else if (version_compare(PHP_VERSION, '7.0.0', '<')) { + $err = "当前版本(" . PHP_VERSION . ")过低,请使用PHP7.0以上版本"; + } else if (!extension_loaded("PDO")) { + $err = "当前未开启PDO,无法进行安装"; + } else if (!is_really_writable($dbConfigFile)) { + $open_basedir = ini_get('open_basedir'); + if ($open_basedir) { + $dirArr = explode(PATH_SEPARATOR, $open_basedir); + if ($dirArr && in_array(__DIR__, $dirArr)) { + $err = '当前服务器因配置了open_basedir,导致无法读取父目录'; + } + } + if (!$err) { + $err = '当前权限不足,无法写入配置文件application/database.php'; + } + } + if ($err) { + $this->error($err); + } + + $mysqlHostname = isset($_POST['mysqlHost']) ? $_POST['mysqlHost'] : '127.0.0.1'; + $mysqlHostport = isset($_POST['mysqlHostport']) ? $_POST['mysqlHostport'] : 3306; + $hostArr = explode(':', $mysqlHostname); + if (count($hostArr) > 1) { + $mysqlHostname = $hostArr[0]; + $mysqlHostport = $hostArr[1]; + } + $mysqlUsername = isset($_POST['mysqlUsername']) ? $_POST['mysqlUsername'] : 'root'; + $mysqlPassword = isset($_POST['mysqlPassword']) ? $_POST['mysqlPassword'] : 'root'; + $mysqlDatabase = isset($_POST['mysqlDatabase']) ? $_POST['mysqlDatabase'] : 'pearProject'; + $mysqlPrefix = isset($_POST['mysqlPrefix']) ? $_POST['mysqlPrefix'] : 'pear_'; + try { + ignore_user_abort(); + set_time_limit(0); + //检测能否读取安装文件 + $sql = @file_get_contents($dataPath . 'pearproject.sql'); + if (!$sql) { + throw new Exception("无法读取data/pearproject.sql文件,请检查是否有读权限"); + } + $sql = str_replace("`pms_", "`{$mysqlPrefix}", $sql); + $pdo = new PDO("mysql:host={$mysqlHostname};port={$mysqlHostport}", $mysqlUsername, $mysqlPassword, array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8" + )); + + //检测是否支持innodb存储引擎 + $pdoStatement = $pdo->query("SHOW VARIABLES LIKE 'innodb_version'"); + $result = $pdoStatement->fetch(); + if (!$result) { + throw new Exception("当前数据库不支持innodb存储引擎,请开启后再重新尝试安装"); + } + + $pdo->query("CREATE DATABASE IF NOT EXISTS `{$mysqlDatabase}` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;"); + + $pdo->query("USE `{$mysqlDatabase}`"); + + $pdo->exec($sql); + + $config = @file_get_contents($dbConfigFile); + $callback = function ($matches) use ($mysqlHostname, $mysqlHostport, $mysqlUsername, $mysqlPassword, $mysqlDatabase, $mysqlPrefix) { + $field = ucfirst($matches[1]); + $replace = ${"mysql{$field}"}; + if ($matches[1] == 'hostport' && $mysqlHostport == 3306) { + $replace = ''; + } + return "'{$matches[1]}'{$matches[2]}=>{$matches[3]}'{$replace}',"; + }; + + $config = preg_replace_callback("/'(hostname|database|username|password|hostport|prefix)'(\s+)=>(\s+)(.*),/", $callback, $config); + //检测能否成功写入数据库配置 + $result = @file_put_contents($dbConfigFile, $config); + + if (!$result) { + throw new Exception("无法写入数据库信息到config/database.php文件,请检查是否有写权限"); + } + + //检测能否成功写入lock文件 + $result = @file_put_contents($lockFile, 1); + if (!$result) { + throw new Exception("无法写入安装锁定到data/install.lock文件,请检查是否有写权限"); + } + $this->success('安装成功,请登录'); + } catch (PDOException $e) { + $err = $e->getMessage(); + } catch (Exception $e) { + $err = $e->getMessage(); + } + if ($err) { + $this->error($err); + } + $this->success('安装成功,请登录'); + + } + + public function checkInstall() + { + $dataPath = env('root_path') . '/data/'; + // 锁定的文件 + $lockFile = $dataPath . '/install.lock'; + if (!is_file($lockFile)) { + $this->error('', 201); + } + $this->success(); + } + + /** + * 获取行政区划数据 + */ + public function getAreaData() + { + $this->success('', Areas::createJsonForAnt()); + + } + + /** + * 将webscoket的client_id和用户id进行绑定 + * @param Request $request + */ + public function bindClientId(Request $request) + { + $clientId = $request::param('client_id'); + $uid = $request::param('uid'); + if (!$uid) { + $uid = session('user.id'); + } + $messageService = new MessageService(); + $messageService->bindUid($clientId, $uid); + $messageService->joinGroup($clientId, 'admin'); + $this->success('', $uid); + } + + public function createNotice(Request $request) + { + $data = $request::post(); + $notifyModel = new \app\common\Model\Notify(); + $result = $notifyModel->add($data['title'], $data['content'], $data['type'], $data['from'], $data['to'], $data['action'], $data['send_data'], $data['terminal']); + $messageService = new MessageService(); + $messageService->sendToUid($data['to'], $data, $data['action']); + $this->success('', $result); + } + + public function pushNotice(Request $request) + { + $uid = $request::param('uid'); + $messageService = new MessageService(); + $messageService->sendToUid($uid, '888', 'notice'); + $this->success(); + + } + + public function pushNoticeGroup(Request $request) + { + $group = $request::param('group'); + $messageService = new MessageService(); + $messageService->sendToGroup($group, '999', 'noticeGroup'); +// $this->success('群组消息', $group); + } +} diff --git a/application/index/controller/Notify.php b/application/index/controller/Notify.php new file mode 100644 index 0000000..025ddd1 --- /dev/null +++ b/application/index/controller/Notify.php @@ -0,0 +1,49 @@ +model) { + $this->model = new \app\common\Model\Notify(); + } + } + + /** + * + * 列表 + * @return void + * @throws \think\exception\DbException + */ + public function listTypeFormat() + { + $where = []; + $params = Request::post(); + if (isset($params['keyword']) && $params['keyword'] !== '') { + $where[] = ['content', 'like', "%{$params['keyword']}%"]; + } + foreach (['to', 'type'] as $key) { + if (isset($params[$key]) && $params[$key] !== '') { + $where[] = [$key, '=', $params[$key]]; + }; + } + $list = $this->model->listTypeFormat($where); + $this->success('', $list); + } + + public function noReads() + { + $projectId = $this->model->gecurrentOrganizationCode(); + $list = $this->model->listTypeFormat(['is_read' => 0, 'to' => 0], 5); + $this->success('', $list); + } + +} diff --git a/application/project/behavior/Project.php b/application/project/behavior/Project.php new file mode 100644 index 0000000..30f015c --- /dev/null +++ b/application/project/behavior/Project.php @@ -0,0 +1,134 @@ + 'log/project']); + $logData = ['member_code' => $data['memberCode'], 'source_code' => $data['sourceCode'], 'remark' => $data['remark'], 'type' => $data['type'], 'content' => $data['content'], 'is_comment' => $data['isComment'], 'to_member_code' => $data['toMemberCode'], 'create_time' => nowTime(), 'code' => createUniqueCode('collection'), 'action_type' => 'project']; + $project = \app\common\Model\Project::where(['code' => $data['sourceCode']])->find(); + $logData['project_code'] = $project['code']; + $toMember = []; + if ($data['toMemberCode']) { + $toMember = Member::where(['code' => $data['toMemberCode']])->find(); + } + $notifyData = [ + 'title' => '', + 'content' => '', + 'type' => '', + 'action' => '', + 'terminal' => '', + ]; + $remark = ''; + $content = ''; + switch ($data['type']) { + case 'create': + $icon = 'plus'; + $remark = '创建了项目 '; + $content = $project['name']; + $notifyData['title'] = ""; + $notifyData['action'] = ""; + break; + case 'edit': + $icon = 'edit'; + $remark = '编辑了项目 '; + $content = $project['name']; + break; + case 'name': + $icon = 'edit'; + $remark = '修改了项目名称 '; + $content = $project['name']; + break; + case 'content': + $icon = 'file-text'; + $remark = '更新了备注 '; + $content = $project['description']; + break; + case 'clearContent': + $icon = 'file-text'; + $remark = '清空了备注 '; + break; + case 'inviteMember': + $icon = 'user-add'; + $remark = '邀请 ' . $toMember['name'] . ' 加入项目'; + $content = $toMember['name']; + break; + case 'removeMember': + $icon = 'user-delete'; + $remark = '移除了成员 ' . $toMember['name']; + $content = $toMember['name']; + break; + case 'recycle': + $icon = 'delete'; + $remark = '把项目移到了回收站 '; + break; + case 'recovery': + $icon = 'undo'; + $remark = '恢复了项目 '; + break; + case 'archive': + $icon = 'delete'; + $remark = '归档了项目 '; + break; + case 'recoveryArchive': + $icon = 'undo'; + $remark = '恢复了项目 '; + break; + case 'uploadFile': + $icon = 'link'; + $remark = '上传了文件文件 '; + $content = "{$data['data']['title']}"; + + break; + case 'deleteFile': + $icon = 'disconnect'; + $remark = '删除了文件 '; + $content = "{$data['data']['title']}"; + break; + default: + $icon = 'plus'; + $remark = ' 创建了项目 '; + break; + } + $logData['icon'] = $icon; + if (!$data['remark']) { + $logData['remark'] = $remark; + } + if (!$data['content']) { + $logData['content'] = $content; + } + ProjectLog::create($logData); + if (false) { + //todo 短信,消息推送 + $notifyModel = new \app\common\Model\Notify(); + $notifyData['content'] = ""; + $result = $notifyModel->add($notifyData['title'], $notifyData['content'], $notifyData['type'], 0, 0, $notifyData['action'], json_encode($project), $notifyData['terminal']); + $organizationCode = getCurrentOrganizationCode(); + $messageService = new MessageService(); + $messageService->sendToAll(['content' => $notifyData['content'], 'title' => $notifyData['title'], 'data' => ['organizationCode' => $organizationCode], 'notify' => $result], $notifyData['action']); + } + } +} diff --git a/application/project/behavior/Task.php b/application/project/behavior/Task.php new file mode 100644 index 0000000..63448d7 --- /dev/null +++ b/application/project/behavior/Task.php @@ -0,0 +1,168 @@ + 'log/task']); + $logData = ['member_code' => $data['memberCode'], 'source_code' => $data['taskCode'], 'remark' => $data['remark'], 'type' => $data['type'], 'content' => $data['content'], 'is_comment' => $data['isComment'], 'to_member_code' => $data['toMemberCode'], 'create_time' => nowTime(), 'code' => createUniqueCode('collection'), 'action_type' => 'task']; + $task = \app\common\Model\Task::where(['code' => $data['taskCode']])->find(); + $logData['project_code'] = $task['project_code']; + $toMember = []; + if ($data['toMemberCode']) { + $toMember = Member::where(['code' => $data['toMemberCode']])->find(); + } + $notifyData = [ + 'title' => '', + 'content' => '', + 'type' => '', + 'action' => '', + 'terminal' => '', + ]; + $remark = ''; + $content = ''; + switch ($data['type']) { + case 'create': + $icon = 'plus'; + $remark = '创建了任务 '; + $content = $task['name']; + $notifyData['title'] = ""; + $notifyData['action'] = ""; + break; + case 'name': + $icon = 'edit'; + $remark = '更新了内容 '; + $content = $task['name']; + break; + case 'content': + $icon = 'file-text'; + $remark = '更新了备注 '; + $content = $task['description']; + break; + case 'clearContent': + $icon = 'file-text'; + $remark = '清空了备注 '; + break; + case 'done': + $icon = 'check'; + $remark = '完成了任务 '; + break; + case 'redo': + $icon = 'border'; + $remark = '重做了任务 '; + break; + case 'createChild': + $icon = 'bars'; + $remark = '添加了子任务 ' . '"' . $data['data']['taskName'] . '"'; + break; + case 'doneChild': + $icon = 'bars'; + $remark = '完成了子任务 ' . '"' . $task['name'] . '"'; + break; + case 'redoChild': + $icon = 'undo'; + $remark = '重做了子任务 ' . '"' . $task['name'] . '"'; + break; + case 'claim': + $icon = 'user'; + $remark = '认领了任务 '; + break; + case 'assign': + $icon = 'user'; + $remark = '指派给了 ' . $toMember['name']; + break; + case 'pri': + $icon = 'user'; + $remark = '更新任务优先级为 ' . $task['priText']; + break; + case 'removeExecutor': + $icon = 'user-delete'; + $remark = '移除了执行者 '; + break; + case 'changeState': + $icon = 'edit'; + $taskState = TaskStages::where(['code' => $task['stage_code']])->find(); + $remark = '将任务移动到 ' . $taskState['name']; + break; + case 'inviteMember': + $icon = 'user-add'; + $remark = '添加了参与者 ' . $toMember['name']; + break; + case 'removeMember': + $icon = 'user-delete'; + $remark = '移除了参与者 ' . $toMember['name']; + break; + case 'setEndTime': + $icon = 'calendar'; + $remark = '更新截止时间为 ' . date('m月d日 H:i', strtotime($task['end_time'])); + break; + case 'clearEndTime': + $icon = 'calendar'; + $remark = '清除了截止时间 '; + break; + case 'recycle': + $icon = 'delete'; + $remark = '把任务移到了回收站 '; + break; + case 'recovery': + $icon = 'undo'; + $remark = '恢复了任务 '; + break; + case 'linkFile': + $icon = 'link'; + $remark = '关联了文件 '; + $content = "{$data['data']['title']}"; + + break; + case 'unlinkFile': + $icon = 'disconnect'; + $remark = '取消关联文件'; + $content = "{$data['data']['title']}"; + break; + default: + $icon = 'plus'; + $remark = ' 创建了任务 '; + break; + } + $logData['icon'] = $icon; + if (!$data['remark']) { + $logData['remark'] = $remark; + } + if (!$data['content']) { + $logData['content'] = $content; + } + ProjectLog::create($logData); + if (false) { + //todo 短信,消息推送 + $notifyModel = new \app\common\Model\Notify(); + $notifyData['content'] = ""; + $result = $notifyModel->add($notifyData['title'], $notifyData['content'], $notifyData['type'], 0, 0, $notifyData['action'], json_encode($task), $notifyData['terminal']); + $organizationCode = getCurrentOrganizationCode(); + $messageService = new MessageService(); + $messageService->sendToAll(['content' => $notifyData['content'], 'title' => $notifyData['title'], 'data' => ['organizationCode' => $organizationCode], 'notify' => $result], $notifyData['action']); + } + } +} diff --git a/application/project/common.php b/application/project/common.php new file mode 100644 index 0000000..5881c81 --- /dev/null +++ b/application/project/common.php @@ -0,0 +1,52 @@ +checkExt(strtolower(sysconf('storage_local_exts')))) { + \exception('不支持该文件类型', 1); + } + $path = $path_name; + $info = $file->move($path, $saveName); + if ($info) { + $filename = str_replace('\\', '/', $path . '/' . $info->getSaveName()); +// $image = \think\Image::open($info->getRealPath()); +// $image->thumb($image->width() / 2, $image->height() / 2)->save($filename);//压缩 + $site_url = FileService::getFileUrl($filename, 'local'); + $fileInfo = FileService::save($filename, file_get_contents($site_url)); + if ($fileInfo) { + return ['base_url' => $fileInfo['key'], 'url' => $fileInfo['url'], 'filename' => $file->getInfo('name'), 'uploadInfo' => $info]; + } + } + return false; +} + +function getCurrentMember() +{ + return session('member'); +} + +function getCurrentOrganizationCode() +{ + return session('currentOrganizationCode'); +} + +function getCurrentOrganization() +{ + return session('organization'); +} + diff --git a/application/project/controller/Account.php b/application/project/controller/Account.php new file mode 100644 index 0000000..4d45f94 --- /dev/null +++ b/application/project/controller/Account.php @@ -0,0 +1,208 @@ +model) { + $this->model = new \app\common\Model\MemberAccount(); + } + } + + /** + * 账户列表 + * @return array|string + * @throws \think\exception\DbException + */ + public function index() + { + $currentOrganizationCode = getCurrentOrganizationCode(); + $where = [['organization_code', '=', $currentOrganizationCode]]; +// $where = [['organization_code', '=', $currentOrganizationCode], ['is_owner', '=', 0]]; + $params = Request::only('account,mobile,email,searchType,keyword'); + $departmentCode = Request::param('departmentCode'); + if (isset($params['keyword']) && $params['keyword']) { + $where[] = ['name', 'like', "%{$params['keyword']}%"]; + } + if (isset($params['searchType'])) { + $searchType = $params['searchType']; + switch ($searchType) { + case 1: + $where[] = ['status', '=', 1]; + break; + case 2: + $where[] = ['department_code', '=', '']; + break; + case 3: + $where[] = ['status', '=', 0]; + break; + case 4: + $where[] = ['status', '=', 1]; + $where[] = ['department_code', 'like', "%{$departmentCode}%"]; + break; + default: + $where[] = ['status', '=', 1]; + + } + } + foreach (['account', 'mobile', 'email'] as $key) { + (isset($params[$key]) && $params[$key] !== '') && $where[] = [$key, 'like', "%{$params[$key]}%"]; + } + if (isset($params['date']) && $params['date'] !== '') { + list($start, $end) = explode('~', $params['date']); + $where[] = ['last_login_time', 'between', ["{$start} 00:00:00", "{$end} 23:59:59"]]; + } + $list = $this->model->_list($where,'id asc'); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $memberInfo = Member::where(['code' => $item['member_code']])->field('id', true)->find(); + if ($memberInfo) { + $item['avatar'] = $memberInfo['avatar']; + } + $departments = ''; + $departmentCodes = $item['department_code']; + if ($departmentCodes) { + $departmentCodes = explode(',', $departmentCodes); + foreach ($departmentCodes as $departmentCode) { + $department = \app\common\Model\Department::where(['code' => $departmentCode])->field('name')->find(); + $departments .= "{$department['name']} "; + } + } + $item['departments'] = $departments; + } + unset($item); + } + $list['authList'] = \app\common\Model\ProjectAuth::where(['status' => '1', 'organization_code' => $currentOrganizationCode])->select(); + $this->success('', $list); + } + + /** + * 授权管理 + * @return array|string + */ + public function auth() + { + $params = Request::only('id,auth'); + $data = ['id' => $params['id']]; + if ($params['auth']) { + // //支持同时设置多个角色,默认关闭 + $data['authorize'] = intval($params['auth']); +// $data['authorize'] = implode(',', json_decode($params['auth'])); + } else { + $data['authorize'] = ''; + } + $result = $this->model->_edit($data); + if ($result) { + $this->success(''); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 账户添加 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function add() + { + //todo 权限判断 + + $params = Request::only('account,mail,mobile,desc,password,realname,name'); + if ($params['password']) { + $params['password'] = md5($params['password']); + } else { + unset($params['password']); + } + $params['create_time'] = nowTime(); + $params['member_code'] = session('member.code'); + $params['organization_code'] = session('currentOrganizationCode'); + $user = $this->model->where(['account' => $params['account'], 'member_code' => session('member.code'), 'organization_code' => session('currentOrganizationCode')])->find(); + $user && $this->error("该账号已存在!"); + $result = $this->model->_add($params); + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 账户编辑 + * @return array|string + */ + public function edit() + { + //todo 权限判断 + + $params = Request::only('mobile,email,desc,name,id,description'); + $result = $this->model->_edit($params, ['id' => $params['id']]); + if ($result) { + $this->success(''); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除账户 + */ + public function del() + { + try { + $result = $this->model->del(Request::post('accountCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('', $result); + } + $this->error("账户删除失败,请稍候再试!"); + } + + /** + * 账户禁用 + */ + public function forbid() + { + //todo 权限判断 + + $params = Request::only('status,accountCode'); + $result = $this->model->_edit(['status' => $params['status']], ['code' => $params['accountCode']]); + if ($result) { + $this->success(''); + } + $this->error("账户禁用失败,请稍候再试!"); + } + + /** + * 账户恢复 + */ + public function resume() + { + //todo 权限判断 + + $params = Request::only('status,accountCode'); + $result = $this->model->_edit(['status' => $params['status']], ['code' => $params['accountCode']]); + if ($result) { + $this->success(''); + } + $this->error("账户启用失败,请稍候再试!"); + } + +} diff --git a/application/project/controller/Auth.php b/application/project/controller/Auth.php new file mode 100644 index 0000000..b92cc20 --- /dev/null +++ b/application/project/controller/Auth.php @@ -0,0 +1,200 @@ +model) { + $this->model = new \app\common\Model\ProjectAuth(); + } + } + + /** + * 权限列表 + * @return array|string + * @throws \think\Exception + * @throws \think\exception\DbException + */ + public function index() + { + $currentOrganizationCode = getCurrentOrganizationCode(); + $this->success('', $this->model->_list(['organization_code' => $currentOrganizationCode])); + } + + /** + * 权限授权 + * @return string + */ + public function apply() + { + $auth_id = Request::param('id', '0'); + $method = '_apply_' . strtolower(Request::param('action', '0')); + if (method_exists($this, $method)) { + return $this->$method($auth_id); + } + } + + /** + * 读取授权节点 + * @param string $auth + */ + protected function _apply_getnode($auth) + { + $nodes = NodeService::get([], [], 'project'); + $checked = ProjectAuthNode::where(['auth' => $auth])->column('node'); + $count = 1; + foreach ($nodes as $key => &$node) { + if (!$node['title']) { + $node['title'] = '-'; + } + $node['checked'] = in_array($node['node'], $checked); + $node['key'] = $node['node']; + $count++; + } + $checkedList = []; + $all = $this->_apply_filter(ToolsService::arr2tree($nodes, 'node', 'pnode', 'children'), 1, $checkedList); + $this->success('', ['list' => $all, 'checkedList' => $checkedList]); + } + + /** + * 保存授权节点 + * @param string $auth + * @throws \Exception + */ + protected function _apply_save($auth) + { + list($data, $post) = [[], Request::only('action,id,nodes')]; + isset($post['nodes']) && $post['nodes'] = json_decode($post['nodes']); + foreach (isset($post['nodes']) ? $post['nodes'] : [] as $node) { + $data[] = ['auth' => $auth, 'node' => $node]; + } + ProjectAuthNode::where(['auth' => $auth])->delete(); + ProjectAuthNode::insertAll($data); + $this->success('节点授权更新成功!', ''); + } + + /** + * 节点数据拼装 + * @param array $nodes + * @param int $level + * @param $checkedList + * @return array + */ + protected function _apply_filter($nodes, $level = 1, &$checkedList) + { + foreach ($nodes as $key => $node) { + if (!empty($node['children']) && is_array($node['children'])) { + $node[$key]['children'] = $this->_apply_filter($node['children'], $level + 1, $checkedList); + } else { + if ($node['checked']) { + $checkedList[] = $node['key']; + } + } + } + return $nodes; + } + + /** + * 权限添加 + * @return array|string + */ + public function add() + { + if (!Request::param('title')) { + $this->error('名称不能为空'); + } + $currentOrganizationCode = getCurrentOrganizationCode(); + $params = Request::only('title,desc,id'); + $params['organization_code'] = $currentOrganizationCode; + $result = $this->model->_add($params); + if ($result) { + $this->success('', $result); + } + } + + /** + * 权限编辑 + * @return array|string + */ + public function edit() + { + if (!Request::param('title')) { + $this->error('名称不能为空'); + } + $result = $this->model->_edit(Request::only('title,desc,id')); + if ($result) { + $this->success(''); + } + $this->error("权限禁用失败,请稍候再试!"); + } + + /** + * 权限禁用 + */ + public function forbid() + { + $result = $this->model->_edit(Request::only('status,id')); + if ($result) { + $this->success(''); + } + $this->error("权限禁用失败,请稍候再试!"); + } + + /** + * 权限恢复 + */ + public function resume() + { + $result = $this->model->_edit(Request::only('status,id')); + if ($result) { + $this->success(''); + } + $this->error("权限启用失败,请稍候再试!"); + } + + /** + * 设置默认权限 + */ + public function setDefault() + { + $currentOrganizationCode = getCurrentOrganizationCode(); + $params = Request::only('is_default,id'); + $this->model->isUpdate(true)->save(['is_default' => 0],['organization_code' => $currentOrganizationCode]); + $result = $this->model->_edit($params); + if ($result) { + $this->success(''); + } + $this->error("权限设置失败,请稍候再试!"); + } + + /** + * 权限删除 + * @throws \Exception + */ + public function del() + { + $result = $this->model->del(Request::only('id')); + if ($result) { + $this->success("权限删除成功!", ''); + } + $this->error("权限删除失败,请稍候再试!"); + } +} diff --git a/application/project/controller/Department.php b/application/project/controller/Department.php new file mode 100644 index 0000000..a924df8 --- /dev/null +++ b/application/project/controller/Department.php @@ -0,0 +1,137 @@ +model) { + $this->model = new \app\common\Model\Department(); + } + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $orgCode = getCurrentOrganizationCode(); + $where = []; + $pcode = Request::post('pcode', ''); + $where[] = ['organization_code', '=', $orgCode]; + $where[] = ['pcode', '=', $pcode]; + $list = $this->model->_list($where,'id asc'); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $item['hasNext'] = false; + $hasNext = $this->model->where(['pcode' => $item['code']])->find(); + if ($hasNext) { + $item['hasNext'] = true; + } + } + } + $this->success('', $list); + } + + + /** + * 获取信息 + * + * @param Request $request + * @return void + * @throws \think\Exception\DbException + */ + public function read(Request $request) + { + $department = $this->model->where(['code' => $request::post('departmentCode')])->field('id', true)->find(); + if (!$department) { + $this->notFound(); + } + $this->success('', $department); + } + + /** + * 新增 + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,parentDepartmentCode'); + if (!$request::post('name')) { + $this->error("请填写部门名称"); + } + try { + $result = $this->model->createDepartment($data['name'], $data['parentDepartmentCode']); + } catch (Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 保存 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit(Request $request) + { + $data = $request::only('name'); + $code = $request::post('departmentCode'); + if (!$code) { + $this->error("请选择一个部门"); + } + $department = $this->model->where(['code' => $code])->field('id')->find(); + if (!$department) { + $this->error("该部门已失效"); + } + $result = $this->model->_edit($data, ['code' => $code]); + if ($result) { + $this->success('',$result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除部门 + * @return void + * @throws \Exception + */ + public function delete() + { + $code = Request::post('departmentCode'); + if (!$code) { + $this->error("请选择一个部门"); + } + $this->model->deleteDepartment($code); + $this->success(''); + } +} diff --git a/application/project/controller/DepartmentMember.php b/application/project/controller/DepartmentMember.php new file mode 100644 index 0000000..4e04042 --- /dev/null +++ b/application/project/controller/DepartmentMember.php @@ -0,0 +1,143 @@ +model) { + $this->model = new \app\common\Model\DepartmentMember(); + } + } + + /** + * @throws \think\exception\DbException + */ + public function index() + { + $organization_code = getCurrentOrganizationCode(); + $department_code = Request::post('departmentCode', ''); + $where = []; + $where[] = ['organization_code', '=', $organization_code]; + $where[] = ['department_code', '=', $department_code]; + $list = $this->model->_list($where, 'is_owner desc'); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $member = MemberAccount::where(['code' => $item['account_code']])->field('name,avatar,code,email')->find(); + !$member && $member = []; + $member['is_owner'] = $item['is_owner']; + $member['is_principal'] = $item['is_principal']; + $item = $member; + } + } + $this->success('', $list); + } + + + /** + * 邀请成员查询 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function searchInviteMember() + { + $keyword = trim(Request::post('keyword')); + $department_code = Request::post('departmentCode',''); + + $orgCode = getCurrentOrganizationCode(); + if (!$keyword && !$department_code) { + $this->success(''); + } + if ($department_code) {//添加部门成员 + $departmentMemberIds = $this->model->where([['organization_code', '=', $orgCode],['department_code', '=', $department_code]])->column('account_code'); + }else{ + //添加组织成员 + $departmentMemberIds = MemberAccount::where([['organization_code', '=', $orgCode]])->column('member_code'); + } + //从当前组织的所有成员查询,判断是否已加入该组织/部门,并存储已加入组织/部门的成员的account_code + $memberAccountList = MemberAccount::where([['name', 'like', "%{$keyword}%"], ['organization_code', '=', $orgCode]])->select()->toArray(); + $tempList = []; + if ($memberAccountList) { + foreach ($memberAccountList as $member) { + $item = []; + //添加组织时,存储member_code,添加部门时存储account_code + $code = $department_code ? $member['code'] : $member['member_code']; + $item['accountCode'] = $code; + $item['avatar'] = $member['avatar']; + $item['name'] = $member['name']; + $item['email'] = $member['email'] ?? '未绑定邮箱'; + $item['joined'] = false; + if (in_array($code, $departmentMemberIds)) { + $item['joined'] = true; + $departmentMemberIds[] = $member['code']; + } + $tempList[$item['accountCode']] = $item; //为了去重 + } + } + if (!$department_code) { + //从平台查询 + $memberList = Member::where([['email', 'like', "%{$keyword}%"]])->whereNotIn('code', $departmentMemberIds)->select()->toArray(); + if ($memberList) { + foreach ($memberList as $member) { + $item = []; + $item['accountCode'] = $member['code']; + $item['avatar'] = $member['avatar']; + $item['name'] = $member['name']; + $item['email'] = $member['email'] ?? '未绑定邮箱'; + $item['joined'] = false; + if (in_array($item['accountCode'], $departmentMemberIds)) { + $item['joined'] = true; + } + $tempList[$item['accountCode']] = $item; //为了去重 + } + } + } + $this->success('', array_values($tempList));//数组下标重置 + } + + /** + * 邀请成员 + */ + public function inviteMember() + { + //部门编号为空,则添加至组织 + $data = Request::only('accountCode,departmentCode'); + if (!$data['accountCode']) { + $this->error('数据异常'); + } + try { + $this->model->inviteMember($data['accountCode'], $data['departmentCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 移除成员 + */ + public function removeMember() + { + $data = Request::only('accountCode,departmentCode'); + if (!$data['accountCode'] || !$data['departmentCode']) { + $this->error('数据异常'); + } + try { + $this->model->removeMember($data['accountCode'], $data['departmentCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } +} diff --git a/application/project/controller/File.php b/application/project/controller/File.php new file mode 100644 index 0000000..33e012b --- /dev/null +++ b/application/project/controller/File.php @@ -0,0 +1,210 @@ +model) { + $this->model = new \app\common\Model\File(); + } + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $orgCode = getCurrentOrganizationCode(); + $memberCode = getCurrentMember()['code']; + $projectCode = Request::param('projectCode'); + $deleted = Request::param('deleted', 0); + if (!$projectCode) { + $this->error("请选择项目"); + } + $where = []; + $where[] = ['organization_code', '=', $orgCode]; + $where[] = ['project_code', '=', $projectCode]; + $where[] = ['create_by', '=', $memberCode]; + $where[] = ['deleted', '=', $deleted]; + $list = $this->model->_list($where); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $item['creatorName'] = ''; + $member = Member::where(['code' => $item['create_by']])->find(); + if ($member) { + $item['creatorName'] = $member['name']; + } + } + } + $this->success('', $list); + } + + + /** + * 获取信息 + * + * @param Request $request + * @return void + * @throws \think\Exception\DbException + */ + public function read(Request $request) + { + $file = $this->model->where(['code' => $request::post('fileCode')])->field('id', true)->find(); + if (!$file) { + $this->notFound(); + } + $this->success('', $file); + } + + + /** + * 文件上传 + * @throws Exception + * @throws \OSS\Core\OssException + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + * @throws \think\exception\PDOException + */ + public function uploadFiles() + { + $data = Request::post(); + $fileName = $data['identifier']; + $orgFileName = $data['filename']; + $chunkNumber = $data['chunkNumber']; + $totalChunks = $data['totalChunks']; + $file = Request::file('file'); + $orgCode = getCurrentOrganizationCode(); + $memberCode = getCurrentMember()['code']; + $date = date('Ymd', time()); + $ticket = date('YmdHis', time()); + $path = config('upload.base_path') . config('upload.file_temp') . "/{$orgCode}/{$memberCode}/$date/"; + $saveName = $fileName . "-{$chunkNumber}"; + try { + $uploadInfo = _uploadFile($file, $path, $saveName); + } catch (\Exception $e) { + $this->error($e->getMessage(), 500); + } + $info = $uploadInfo['uploadInfo']; + + $fileData = [ + 'extension' => $info->getExtension(), + 'file_type' => $info->getInfo()['type'], + ]; + $result = []; + if ($chunkNumber == $totalChunks) { + $fileList = []; + $blob = ''; + for ($i = 1; $i <= $totalChunks; $i++) { + $ext = explode('.', $orgFileName); + $ext = $ext[count($ext) - 1]; + $fileUrl = "{$path}/{$fileName}-{$i}.{$ext}"; + $site_url = FileService::getFileUrl($fileUrl, 'local'); + $blob .= file_get_contents($site_url); + $fileList[] = env('root_path') . $fileUrl; + } + $path = config('upload.base_path') . config('upload.file') . "/{$orgCode}/{$memberCode}/$date/$ticket-$orgFileName"; + $result = FileService::local($path, $blob); + $fileData['size'] = $data['totalSize']; + $fileData['path_name'] = $result['key']; + $fileData['file_url'] = $result['url']; + $fileData['title'] = FileService::removeSuffix($data['filename']); + $fileData['size'] = $data['totalSize']; + !isset($data['taskCode']) && $data['taskCode'] = ''; + $fileResult = \app\common\Model\File::createFile($data['projectCode'], $fileData); + foreach ($fileList as $file) { + @unlink($file); + } + $fileInfo = \app\common\Model\File::where(['code' => $fileResult['code']])->find(); + if ($data['taskCode']) { + \app\common\Model\SourceLink::createSource('file', $fileResult['code'], 'task', $data['taskCode']); + } + \app\common\Model\Project::projectHook(getCurrentMember()['code'], $data['projectCode'], 'uploadFile','',0,'','',$fileResult['code'],['title' => $fileInfo['fullName'], 'url' => $fileResult['file_url']]); + } + + $project = \app\common\Model\Project::where(['code' => $data['projectCode']])->find(); + $result['projectName'] = $project['name']; + $this->success('', $result); + } + + /** + * 保存 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit(Request $request) + { + $data = $request::only('title'); + $code = $request::post('fileCode'); + if (!$code) { + $this->error("请选择一个文件"); + } + $file = $this->model->where(['code' => $code])->field('id')->find(); + if (!$file) { + $this->error("该文件已失效"); + } + $result = $this->model->_edit($data, ['code' => $code]); + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 放入回收站 + */ + public function recycle() + { + try { + $this->model->recycle(Request::post('fileCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 恢复 + */ + public function recovery() + { + try { + $this->model->recovery(Request::post('fileCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 删除文件 + * @return void + * @throws \Exception + */ + public function delete() + { + $code = Request::post('fileCode'); + if (!$code) { + $this->error("请选择一个文件"); + } + $this->model->deleteFile($code); + $this->success(''); + } +} diff --git a/application/project/controller/Index.php b/application/project/controller/Index.php new file mode 100644 index 0000000..38eb4eb --- /dev/null +++ b/application/project/controller/Index.php @@ -0,0 +1,162 @@ +success('', $list); + } + + /** + * 更换当前组织 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function changeCurrentOrganization() + { + $organizationCode = Request::post('organizationCode'); + if ($organizationCode) { + $member = getCurrentMember(); + $memberAccount = MemberAccount::where(['member_code' => $member['code'], 'organization_code' => $organizationCode])->find(); + $member = Member::where(['account' => $member['account']])->order('id asc')->find()->toArray(); + $member['account_id'] = $memberAccount['id']; + $member['is_owner'] = $memberAccount['is_owner']; + $member['authorize'] = $memberAccount['authorize']; + session('member', $member); + !empty($member['authorize']) && NodeService::applyProjectAuthNode(); + session('currentOrganizationCode', $organizationCode); + + $list = MemberAccount::getAuthMenuList(); + $this->success('', $list); + } + $this->error('请选择组织'); + } + + /** + * 系统信息 + */ + public function systemConfig() + { + $configModel = new SystemConfig(); + $this->success('', $configModel->info()); + + } + + + /** + * 个人个信息 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function info() + { + $this->success('', Member::find(session('project.id'))); + } + + /** + * 个人资料修改 + * @return array|string + */ + public function editPersonal() + { + $params = Request::only('mobile,mail,idcard,name,realname,avatar,id'); + $memberModel = new Member(); + $result = $memberModel->_edit($params, ['id' => Request::post('id')]); + if ($result) { + $this->success('基本信息更新成功'); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 密码修改 + * @return array|string + * @throws \think\Exception\DbException + */ + public function editPassword() + { + $memberModel = new Member(); + $params = Request::only('password,newPassword,confirmPassword,id'); + $member = $memberModel->field('password')->get($params['id'])->toArray(); + if (strlen($params['password']) < 6 || strlen($params['newPassword']) < 6 || strlen($params['confirmPassword']) < 6) { + $this->error("密码必须包含6个字符"); + } + if ($params['newPassword'] != $params['confirmPassword']) { + $this->error("两次新密码不匹配"); + } + $oldPassword = $member['password']; + if ($params['password'] != $oldPassword) { + $this->error("原密码不正确"); + } + $result = $memberModel->_edit(['password' => $params['newPassword']], ['id' => $params['id']]); + if ($result) { + $this->success('密码修改成功'); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * @return void + * @throws \OSS\Core\OssException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function uploadImg() + { + $model = new CommonModel(); + $files = Request::file('image'); + $data = [ + 'errno' => 0, + 'data' => [] + ]; + if ($files) { + foreach ($files as $file) { + $result = $model->_uploadImg($file, config('upload.base_path') . config('upload.image')); + $data['data'][] = $result['url']; + } + } + echo json_encode($data, JSON_UNESCAPED_UNICODE); + die; + } + + /** + * 上传头像 + */ + public function uploadAvatar() + { + $accountModel = new MemberAccount(); + try { + $file = $accountModel->uploadImg(Request::file('avatar')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success('', $file); + } +} diff --git a/application/project/controller/Login.php b/application/project/controller/Login.php new file mode 100644 index 0000000..11093b4 --- /dev/null +++ b/application/project/controller/Login.php @@ -0,0 +1,174 @@ +request->isGet()) { +// var_dump(11);die; +// return $this->fetch('', ['title' => '用户登录']); +// } + // 输入数据效验 +// $validate = Validate::make([ +// 'account' => 'require|min:4', +// 'password' => 'require|min:4', +// ], [ +// 'account.require' => '登录账号不能为空!', +// 'account.min' => '登录账号长度不能少于4位有效字符!', +// 'password.require' => '登录密码不能为空!', +// 'password.min' => '登录密码长度不能少于4位有效字符!', +// ]); + $data = [ + 'account' => $this->request->post('account', ''), + 'password' => $this->request->post('password', ''), + ]; +// $validate->check($data) || $this->error($validate->getError()); + // 用户信息验证 + $mobile = $this->request->post('mobile', ''); + if ($mobile) { + $member = \app\common\Model\Member::where(['mobile' => $mobile])->order('id asc')->find(); + } else { + $member = \app\common\Model\Member::where(['account' => $data['account']])->whereOr(['email' => $data['account']])->order('id asc')->find(); + } + empty($member) && $this->error('账号或密码错误', 201); + $member = $member->toArray(); + empty($member['status']) && $this->error('账号已经被禁用'); + if (!$mobile) { + $member['password'] !== $data['password'] && $this->error('账号或密码错误', 201); + } + // 更新登录信息 + Db::name('Member')->where(['id' => $member['id']])->update([ + 'last_login_time' => Db::raw('now()'), + ]); + $list = \app\common\Model\MemberAccount::where(['member_code' => $member['code']])->order('id asc')->select()->toArray(); + $organizationList = []; + if ($list) { + foreach ($list as $item) { + $organization = Organization::where(['code' => $item['organization_code']])->find()->toArray(); + if ($organization) { + $organizationList[] = $organization; + } + } + } + $member['account_id'] = $list[0]['id']; + $member['is_owner'] = $list[0]['is_owner']; + $member['authorize'] = $list[0]['authorize']; + $member['position'] = $list[0]['position']; + $member['department'] = $list[0]['department']; + + session('member', $member); + !empty($member['authorize']) && NodeService::applyProjectAuthNode(); + Log::write(json_encode($member), "member-login"); + $this->success('', ['member' => $member, 'organizationList' => $organizationList]); + } + + public function getCaptcha() + { + $this->success('', RandomService::numeric(6)); + } + + public function register() + { + $data = Request::only('email,name,password,password2,mobile,captcha'); + $validate = Validate::make([ + 'email' => 'require', + 'name' => 'require', + 'password' => 'require|min:6', + 'password2' => 'require|min:6', + 'mobile' => 'require|min:11', + 'captcha' => 'require|min:6', + ], [ + 'email.require' => '邮箱账号不能为空!', + 'name.require' => '姓名不能为空!', + 'password.require' => '登陆密码不能为空!', + 'password.min' => '登录密码长度不能少于6位有效字符!', + 'password2.require' => '确认密码不能为空!', + 'password2.min' => '确认密码长度不能少于6位有效字符!', + 'mobile.require' => '手机号码不能为空!', + 'mobile.min' => '手机号码格式有误', + 'captcha.require' => '验证码不能为空!', + 'captcha.min' => '验证码格式有误', + ]); + $validate->check($data) || $this->error($validate->getError()); + $member = Member::where(['email' => $data['email']])->field('id')->find(); + if ($member) { + $this->error('该邮箱已被注册', 201); + } + $member = Member::where(['mobile' => $data['mobile']])->field('id')->find(); + if ($member) { + $this->error('该手机已被注册', 202); + } + $memberData = [ + 'email' => $data['email'], + 'name' => $data['name'], + 'account' => RandomService::alnumLowercase(), + 'avatar' => 'https://static.vilson.xyz/cover.png', + 'status' => 1, + 'code' => createUniqueCode('member'), + 'password' => $data['password'], + 'mobile' => $data['mobile'], + ]; + try { + $result = Member::createMember($memberData); + } catch (\Exception $e) { + $this->error($e->getMessage(), 205); + } + if (!$result) { + $this->error('注册失败', 203); + } + $this->success(''); + } + + /** + * 退出登录 + */ + public function out() + { + session('user') && LogService::write('系统管理', '用户退出系统成功'); + !empty($_SESSION) && $_SESSION = []; + [session_unset(), session_destroy()]; + $this->success('退出登录成功!'); + } + +} diff --git a/application/project/controller/Menu.php b/application/project/controller/Menu.php new file mode 100644 index 0000000..4690e50 --- /dev/null +++ b/application/project/controller/Menu.php @@ -0,0 +1,125 @@ +model) { + $this->model = new ProjectMenu; + } + } + + + /** + * 菜单列表 + * @return array|string + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function menu() + { + $list = $this->model->treeList(); + $this->success('', $list); + + } + + /** + * 添加菜单 + * @return array|string + */ + public function menuAdd() + { + $title = Request::post('title'); + $url = Request::post('url'); + if (!$title) { + $this->error('名称不能为空'); + } + if (!$url) { + $this->error('地址不能为空'); + } + $data = Request::only('title,url,icon,is_inner,pid,status,sort,params,values,node,show_slider,file_path'); + if (!$data['file_path']) { + $data['file_path'] = $data['url']; + } + if ($data['is_inner'] == 'true' or $data['is_inner'] == 1) { + $data['is_inner'] = 1; + } else { + $data['is_inner'] = 0; + } + if ($data['show_slider'] == 'true' or $data['show_slider'] == 1) { + $data['show_slider'] = 1; + } else { + $data['show_slider'] = 0; + } + $this->success('', $this->model->_add($data)); + } + + /** + * 编辑菜单 + * @return array|string + */ + public function menuEdit() + { + $data = Request::only('title,url,icon,is_inner,id,params,sort,values,node,show_slider,file_path'); + if (isset($data['title']) and !$data['title']) { + $this->error('名称不能为空'); + } + if (!isset($data['url']) and !$data['url']) { + $this->error('地址不能为空'); + } + if (!$data['file_path']) { + $data['file_path'] = $data['url']; + } + if ($data['is_inner'] == 'true' or $data['is_inner'] == 1) { + $data['is_inner'] = 1; + } else { + $data['is_inner'] = 0; + } + if ($data['show_slider'] == 'true' or $data['show_slider'] == 1) { + $data['show_slider'] = 1; + } else { + $data['show_slider'] = 0; + } + $this->success('', $this->model->_edit($data)); + } + + /** + * 菜单禁用 + */ + public function menuForbid() + { + $this->success('', $this->model->_edit(Request::only('status,id'))); + } + + /** + * 菜单启用 + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function menuResume() + { + $this->success('', $this->model->_edit(Request::only('status,id'))); + } + + + /** + * 删除菜单 + */ + public function menuDel() + { + $this->success('', $this->model->del(Request::post('id'))); + } +} diff --git a/application/project/controller/Node.php b/application/project/controller/Node.php new file mode 100644 index 0000000..824d58c --- /dev/null +++ b/application/project/controller/Node.php @@ -0,0 +1,121 @@ +success('', ['nodes' => $nodes, 'groups' => $groups]); + } + + public function allList() + { +// $module = Request::post('module'); + $node = Request::post('node'); + $where = []; +// if ($module) { +// $where[] = ['module', '=', $module]; +// } + if ($node) { + $where[] = ['node', 'like', "%{$node}%"]; + } + $list = Db::name('ProjectNode')->where($where)->select(); + if ($node == '#') { + $list[] = ['node' => '#', 'title' => '无节点权限']; + } + $this->success('', $list); + } + + /** + * 清理无效的节点记录 + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function clear() + { + $nodes = array_keys(NodeService::get()); + if (false !== Db::name('projectNode')->whereNotIn('node', $nodes)->delete()) { + $this->success('清理无效节点记录成功!', ''); + } + $this->error('清理无效记录失败,请稍候再试!'); + } + + /** + * 保存节点变更 + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public function save() + { + if ($this->request->isPost()) { + list($post, $data) = [json_decode($this->request->post('list')), []]; + foreach ($post as $vo) { + if (!empty($vo->node)) { + $data['node'] = $vo->node; + if ($vo->name) { + foreach ($vo->name as $key => $name) { + $data[$name->name] = $vo->value[$key]->value; + } + } + } + //根据模块将权限节点插入对应的表 + if (strpos('/', $data['node']) !== -1) { + $module = explode('/', $data['node'])[0]; + } else { + $module = $data['node']; + } + $data['module'] = $module; + switch ($module) { + case 'project': + $table = 'ProjectNode'; + break; + default: +// $table = $this->table; + } + !empty($data) && isset($table) && DataService::save($table, $data, 'node'); +// !empty($data) && DataService::save($this->table, $data, 'node'); + + } + $this->success('参数保存成功!', ''); + } + $this->error('访问异常,请重新进入...'); + } + +} diff --git a/application/project/controller/Notify.php b/application/project/controller/Notify.php new file mode 100644 index 0000000..bf586ad --- /dev/null +++ b/application/project/controller/Notify.php @@ -0,0 +1,132 @@ +model) { + $this->model = new \app\common\Model\Notify(); + } + $this->session = $this->model->getMemberSession(); + } + + /** + * 显示资源列表 + * + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $where = []; + $where[] = ['to', '=', $this->model->gecurrentOrganizationCode()]; + $where[] = ['terminal', '=', 'organization']; + $params = Request::post(); + foreach (['title'] as $key) { + (isset($params[$key]) && $params[$key] !== '') && $where[] = [$key, 'like', "%{$params[$key]}%"]; + } + foreach (['type'] as $key) { + (isset($params[$key]) && $params[$key] !== '') && $where[] = [$key, '=', $params['type']]; + } + if (isset($params['date']) && $params['date'] !== '') { + list($start, $end) = explode('~', $params['date']); + $where[] = ['create_time', 'between', ["{$start} 00:00:00", "{$end} 23:59:59"]]; + } + + $result = $this->model->_list($where); + + $this->success('', $result); + + + } + + /** + * 获取未读消息 + */ + public function noReads() + { + $organization_code = $this->model->gecurrentOrganizationCode(); + $list = $this->model->listTypeFormat(['is_read' => 0, 'to' => $organization_code, 'terminal' => 'organization'], 5); + $this->success('', $list); + } + + /** + * 设置已读 + * @throws \Exception + */ + public function setReadied() + { + $ids = Request::post('ids'); + if (!$ids) { + $this->error("请选择消息!"); + } + $ids = json_decode($ids); + $updateData = []; + foreach ($ids as $id) { + $data = [ + 'id' => $id, + 'is_read' => 1, + 'read_time' => nowTime(), + ]; + $updateData[] = $data; + } + $result = $this->model->saveAll($updateData); + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 批量删除 + * @throws \Exception + */ + public function batchDel() + { + $ids = Request::post('ids'); + if (!$ids) { + $this->error("请选择消息!"); + } + $ids = json_decode($ids); + $result = $this->model->whereIn('id', $ids)->delete(); + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + + } + + /** + * 获取信息 + * + * @param Request $request + * @return void + * @throws \think\Exception\DbException + */ + public function read(Request $request) + { + $this->success('', $this->model->get($request::post('id'))); + } + + + + /** + * 删除指定资源 + * + * @param int $id + * @return \think\Response + */ + public function delete($id = 0) + { + $this->model->destroy(Request::post('id')); + $this->success(''); + } +} diff --git a/application/project/controller/Organization.php b/application/project/controller/Organization.php new file mode 100644 index 0000000..a6c8a70 --- /dev/null +++ b/application/project/controller/Organization.php @@ -0,0 +1,114 @@ +model) { + $this->model = new \app\common\Model\Organization(); + } + } + + /** + * 显示资源列表 + * + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $list = \app\common\Model\MemberAccount::where(['member_code' => getCurrentMember()['code']])->order('id asc')->select()->toArray(); + $organizationList = []; + if ($list) { + foreach ($list as $item) { + $organization = $this->model->where(['code' => $item['organization_code']])->field('id', true)->find()->toArray(); + if ($organization) { + $organizationList[] = $organization; + } + } + } + $result = ['list' => $organizationList, 'total' => count($list)]; + $this->success('', $result); + + + } + + /** + * 新增 + * + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,address,areas'); + +// list($data['province'], $data['city'], $data['area']) = json_decode($data['areas']); +// unset($data['areas']); + + try { + $result = \app\common\Model\Organization::createOrganization(getCurrentMember(), $data); + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 获取信息 + * + * @param Request $request + * @return void + * @throws \think\Exception\DbException + */ + public function read(Request $request) + { + $this->success('', $this->model->get($request::post('organizationCode'))); + } + + /** + * 保存 + * + * @param Request $request + * @return void + * @throws \Exception + */ + public function edit(Request $request) + { + $data = $request::only('name,address,areas'); + $organizationCode = $request::param('organizationCode'); + +// list($data['province'], $data['city'], $data['area']) = json_decode($data['areas']); +// unset($data['areas']); + + $result = $this->model->edit($organizationCode, $data); + if ($result) { + $this->success(''); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除指定资源 + * + * @param int $id + * @return void + */ + public function delete($id = 0) + { + $this->model->destroy(Request::post('id')); + $this->success(''); + } +} diff --git a/application/project/controller/Project.php b/application/project/controller/Project.php new file mode 100644 index 0000000..5c3e5e7 --- /dev/null +++ b/application/project/controller/Project.php @@ -0,0 +1,356 @@ +model) { + $this->model = new \app\common\Model\Project(); + } + } + + /** + * 显示资源列表 + * + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $where = []; + $type = Request::post('type'); + $data = Request::only('recycle,archive,all'); + $member = getCurrentMember(); + + $where[] = ['member_code', '=', $member['code']]; + if ($type == 'my' || $type == 'other') { + $projectMemberModel = new ProjectMember(); + $list = $projectMemberModel->_list($where); + } else { + $projectCollectionModel = new ProjectCollection(); + $where[] = ['member_code', '=', $member['code']]; + $list = $projectCollectionModel->_list($where); + } + $newList = []; + if ($list['list']) { + $currentMember = getCurrentMember(); + foreach ($list['list'] as $key => &$item) { + $delete = false; + $project = $this->model->where(['code' => $item->project_code])->find(); + if (!$project) { + $delete = true; + } + if ($type != 'other') { + if ($project['deleted']) { + $delete = true; + } + } + if (isset($data['archive']) && !$project['archive']) { + $delete = true; + } + if (isset($data['recycle']) && !$project['deleted']) { + $delete = true; + } + if ($delete) { + continue; + } + $item['collected'] = false; + $item['owner_name'] = '-'; + if (isset($item['project_code'])) { + $item['code'] = $item['project_code']; + $item = $this->model->where(['code' => $item['code']])->find(); + } + $collected = ProjectCollection::where(['project_code' => $item['code'], 'member_code' => $currentMember['code']])->field('id')->find(); + if ($collected) { + $item['collected'] = true; + } + + $owner = ProjectMember::where(['project_code' => $item['code'], 'is_owner' => 1])->field('member_code')->find(); + if (!$owner) { + continue; + } + $member = Member::where(['code' => $owner['member_code']])->field('name')->find(); + if (!$member) { + continue; + } + $item['owner_name'] = $member['name']; + $newList[] = $item; + } + } + $this->success('', ['list' => $newList, 'total' => count($newList)]); + } + + /** + * 获取自己的任务 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function selfList() + { + $type = Request::post('type'); + $member = getCurrentMember(); + $deleted = 1; + if (!$type) { + $deleted = 0; + } + $list = $this->model->getMemberProjects($member['code'], $deleted, Request::post('page'), Request::post('pageSize')); + if ($list['list']) { + foreach ($list['list'] as $key => &$item) { + $item['collected'] = false; + $item['owner_name'] = '-'; + if (isset($item['project_code'])) { + $item['code'] = $item['project_code']; + $item = $this->model->where(['code' => $item['code']])->find(); + } + $collected = ProjectCollection::where(['project_code' => $item['code'], 'member_code' => getCurrentMember()['code']])->field('id')->find(); + if ($collected) { + $item['collected'] = true; + } + + $owner = ProjectMember::where(['project_code' => $item['code'], 'is_owner' => 1])->field('member_code')->find(); + $member = Member::where(['code' => $owner['member_code']])->field('name')->find(); + $item['owner_name'] = $member['name']; + } + } + $this->success('', $list); + } + + /** + * 新增 + * + * @param Request $request + * @return void + * @throws \Exception + */ + public function save(Request $request) + { + $data = $request::only('name,description,templateCode'); + if (!$request::post('name')) { + $this->error("请填写项目名称"); + } + $data['organization_code'] = getCurrentOrganizationCode(); + $member = getCurrentMember(); + try { + $result = $this->model->createProject($member['code'], $data['organization_code'], $data['name'], $data['description'], $data['templateCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 获取信息 + * + * @param Request $request + * @return void + * @throws \think\Exception\DbException + */ + public function read(Request $request) + { + $project = $this->model->where(['code' => $request::post('projectCode')])->field('id', true)->find(); + if (!$project) { + $this->notFound(); + } + $project['collected'] = false; + $collected = ProjectCollection::where(['project_code' => $project['code'], 'member_code' => getCurrentMember()['code']])->field('id')->find(); + if ($collected) { + $project['collected'] = true; + } + $item['owner_name'] = ''; + $item['owner_avatar'] = ''; + $owner = ProjectMember::where(['project_code' => $project['code'], 'is_owner' => 1])->field('member_code')->find(); + if ($owner) { + $member = Member::where(['code' => $owner['member_code']])->field('name,avatar')->find(); + if ($member) { + $project['owner_name'] = $member['name']; + $project['owner_avatar'] = $member['avatar']; + } + } + $this->success('', $project); + } + + /** + * 保存 + * + * @param Request $request + * @return void + * @throws \Exception + */ + public function edit(Request $request) + { + $data = $request::only('name,description,cover,private,prefix,open_prefix,schedule'); + $code = $request::param('projectCode'); + try { + $result = $this->model->edit($code, $data); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + + } + if ($result) { + $this->success(); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 相关的项目动态 + */ + public function getLogBySelfProject() + { + $projectCode = Request::param('projectCode', ''); + $member = getCurrentMember(); + if (!$member) { + $this->success('', []); + } + $prefix = config('database.prefix'); + if (!$projectCode) { + $where = []; + $where[] = ['member_code', '=', $member['code']]; + $projectCodes = ProjectMember::where($where)->column('project_code'); + if (!$projectCodes) { + $this->success('', []); + } + foreach ($projectCodes as &$projectCode) { + $projectCode = "'{$projectCode}'"; + } + $projectCodes = implode(',', $projectCodes); + $sql = "select tl.remark as remark,tl.content as content,tl.is_comment as is_comment,tl.create_time as create_time,p.name as project_name,t.name as task_name,t.code as source_code,p.code as project_code,m.avatar as member_avatar,m.name as member_name from {$prefix}project_log as tl join {$prefix}task as t on tl.source_code = t.code join {$prefix}project as p on t.project_code = p.code join {$prefix}member as m on tl.member_code = m.code where tl.action_type = 'task' and p.code in ({$projectCodes}) and p.deleted = 0 order by tl.id desc limit 0,20"; +// $sql = "select tl.remark as remark,tl.content as content,tl.is_comment as is_comment,tl.create_time as create_time,p.name as project_name,p.code as project_code,m.avatar as member_avatar,m.name as member_name from {$prefix}project_log as tl join {$prefix}project as p on tl.project_code = p.code join {$prefix}member as m on tl.member_code = m.code where p.code in ({$projectCodes}) and p.deleted = 0 order by tl.id desc limit 0,20"; + $list = Db::query($sql); + } else { + $page = Request::param('page'); + $pageSize = Request::param('pageSize'); + if ($page < 1) { + $page = 1; + } + $offset = $pageSize * ($page - 1); + $sql = "select tl.type as type,tl.action_type as action_type,tl.source_code as source_code,tl.remark as remark,tl.content as content,tl.is_comment as is_comment,tl.create_time as create_time,p.name as project_name,p.code as project_code,m.avatar as member_avatar,m.name as member_name from {$prefix}project_log as tl join {$prefix}project as p on tl.project_code = p.code join {$prefix}member as m on tl.member_code = m.code where p.code = '{$projectCode}' and p.deleted = 0 order by tl.id desc"; + $list = Db::query($sql); + $total = count($list); + $sql .= " limit {$offset},{$pageSize}"; + $list = Db::query($sql); + if ($list) { + foreach ($list as &$item) { + $item['sourceInfo'] = []; + switch ($item['action_type']) { + case 'task': + $item['sourceInfo'] = \app\common\Model\Task::where(['code' => $item['source_code']])->find(); + break; + case 'project': + $item['sourceInfo'] = \app\common\Model\Project::where(['code' => $item['source_code']])->find(); + break; + } + } + } + $list = ['total' => $total, 'list' => $list]; + } + $this->success('', $list); + } + + /** + * 上传封面 + */ + public function uploadCover() + { + try { + $file = $this->model->uploadCover(Request::file('cover')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success('', $file); + } + + /** + * 放入回收站 + */ + public function recycle() + { + try { + $this->model->recycle(Request::post('projectCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 恢复 + */ + public function recovery() + { + try { + $this->model->recovery(Request::post('projectCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + + /** + * 归档 + */ + public function archive() + { + try { + $this->model->archive(Request::post('projectCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 恢复归档 + */ + public function recoveryArchive() + { + try { + $this->model->recoveryArchive(Request::post('projectCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 退出项目 + */ + public function quit() + { + try { + $this->model->quit(Request::post('projectCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + +} diff --git a/application/project/controller/ProjectCollect.php b/application/project/controller/ProjectCollect.php new file mode 100644 index 0000000..bdd3b14 --- /dev/null +++ b/application/project/controller/ProjectCollect.php @@ -0,0 +1,42 @@ +model) { + $this->model = new \app\common\Model\ProjectCollection(); + } + } + + /** + * 收藏项目 + */ + public function collect() + { + $data = Request::only('projectCode,type'); + if (!$data['projectCode']) { + $this->error('请先选择项目'); + } + $project = \app\common\Model\Project::where(['code' => $data['projectCode']])->field('code')->find(); + try { + $member = getCurrentMember(); + $this->model->collect($member['code'], $project['code'], $data['type']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $str = $data['type'] == 'collect' ? '加入收藏成功' : '取消收藏成功'; + $this->success($str); + } +} diff --git a/application/project/controller/ProjectMember.php b/application/project/controller/ProjectMember.php new file mode 100644 index 0000000..b3c42cd --- /dev/null +++ b/application/project/controller/ProjectMember.php @@ -0,0 +1,136 @@ +model) { + $this->model = new \app\common\Model\ProjectMember(); + } + } + + /** + * @throws \think\exception\DbException + */ + public function index() + { + $project_code = Request::post('projectCode'); + $where = []; + $where[] = ['project_code', '=', $project_code]; + $list = $this->model->_list($where, 'is_owner desc'); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $member = Member::where(['code' => $item['member_code']])->field('id,name,avatar,code,email')->find(); + !$member && $member = []; + $member['is_owner'] = $item['is_owner']; + $item = $member; + } + } + $this->success('', $list); + } + + + /** + * 邀请成员查询 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function searchInviteMember() + { + $keyword = trim(Request::post('keyword')); + $code = trim(Request::post('projectCode')); + if (!$code) { + $this->error('请先选择项目'); + } + $orgCode = getCurrentOrganizationCode(); + if (!$keyword) { + $this->success(''); + } + $project = \app\common\Model\Project::where(['code' => $code])->field('id')->find(); + //先找出项目所有成员 + $projectMemberIds = $this->model->where([['project_code', '=', $code]])->column('member_code'); + $tempList = []; + //从当前组织的所有成员查询,判断是否已加入该项目,并存储已加入项目的成员的account_id + $memberAccountList = MemberAccount::where([['name', 'like', "%{$keyword}%"], ['organization_code', '=', $orgCode]])->select()->toArray(); + if ($memberAccountList) { + foreach ($memberAccountList as $member) { + $item = []; + $item['memberCode'] = $member['member_code']; + $item['status'] = $member['status']; + $item['avatar'] = $member['avatar']; + $item['name'] = $member['name']; + $item['email'] = $member['email'] ?? '未绑定邮箱'; + $item['joined'] = false; + if (in_array($member['member_code'], $projectMemberIds)) { + $item['joined'] = true; + $projectMemberIds[] = $member['member_code']; + } + $tempList[$item['memberCode']] = $item; //为了去重 + } + } + //从平台查询 + $memberList = Member::where([['email', 'like', "%{$keyword}%"]])->whereNotIn('code', $projectMemberIds)->select()->toArray(); + if ($memberList) { + foreach ($memberList as $member) { + $item = []; + $item['memberCode'] = $member['code']; + $item['status'] = 1; + $item['avatar'] = $member['avatar']; + $item['name'] = $member['name']; + $item['email'] = $member['email'] ?? '未绑定邮箱'; + $item['joined'] = false; + if (in_array($item['memberCode'], $projectMemberIds)) { + $item['joined'] = true; + } + $tempList[$item['memberCode']] = $item; //为了去重 + } + } + $this->success('', array_values($tempList));//数组下标重置 + } + + /** + * 邀请成员 + */ + public function inviteMember() + { + $data = Request::only('memberCode,projectCode'); + if (!$data['memberCode'] || !$data['projectCode']) { + $this->error('数据异常'); + } + try { + $this->model->inviteMember($data['memberCode'], $data['projectCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 移除成员 + */ + public function removeMember() + { + $data = Request::only('memberCode,projectCode'); + if (!$data['memberCode'] || !$data['projectCode']) { + $this->error('数据异常'); + } + try { + $this->model->removeMember($data['memberCode'], $data['projectCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } +} diff --git a/application/project/controller/ProjectTemplate.php b/application/project/controller/ProjectTemplate.php new file mode 100644 index 0000000..bacd27c --- /dev/null +++ b/application/project/controller/ProjectTemplate.php @@ -0,0 +1,141 @@ +model) { + $this->model = new \app\common\Model\ProjectTemplate(); + } + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $orgCode = getCurrentOrganizationCode(); + $where = []; + $viewType = Request::post('viewType', -1); + if ($viewType == 1) { + $where[] = ['is_system', '=', $viewType]; + } + if ($viewType == 0) { + $where[] = ['organization_code', '=', $orgCode]; + $where[] = ['is_system', '=', 0]; + } + $list = $this->model->_list($where); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $item['task_stages'] = \app\common\Model\TaskStagesTemplate::where(['project_template_code' => $item['code']])->field('name')->order('sort desc,id asc')->select(); + } + } + $this->success('', $list); + } + + /** + * 新增 + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,description,cover'); + if (!$request::post('name')) { + $this->error("请填写模板名称"); + } + $data['organization_code'] = getCurrentOrganizationCode(); + $member = getCurrentMember(); + try { + $result = $this->model->createProjectTemplate($member['code'], $data['organization_code'], $data['name'], $data['description'], $data['cover']); + } catch (Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('制作模板成功', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 上传封面 + */ + public function uploadCover() + { + try { + $file = $this->model->uploadCover(Request::file('cover')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success('', $file); + } + + /** + * 保存 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit(Request $request) + { + $data = $request::only('name,description,cover'); + $code = $request::post('code'); + if (!$code) { + $this->error("请选择一个模板"); + } + $template = $this->model->where(['code' => $code])->field('is_system')->find(); + if (!$template) { + $this->error("该模板已失效"); + } + if ($template['is_system']) { + $this->error("无法编辑该模板"); + } + $result = $this->model->_edit($data, ['code' => $code]); + if ($result) { + $this->success('编辑模板成功'); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除模板 + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function delete() + { + $code = Request::post('code'); + if (!$code) { + $this->error("请选择一个模板"); + } + $this->model->deleteTemplate($code); + $this->success(''); + } +} diff --git a/application/project/controller/SourceLink.php b/application/project/controller/SourceLink.php new file mode 100644 index 0000000..dcc5985 --- /dev/null +++ b/application/project/controller/SourceLink.php @@ -0,0 +1,48 @@ +model) { + $this->model = new \app\common\Model\SourceLink(); + } + } + + + /** + * 删除资源 + * @return void + * @throws \Exception + */ + public function delete() + { + $code = Request::post('sourceCode'); + if (!$code) { + $this->error("资源不存在"); + } + $this->model->deleteSource($code); + $this->success(''); + } +} diff --git a/application/project/controller/Task.php b/application/project/controller/Task.php new file mode 100644 index 0000000..a8ae1fd --- /dev/null +++ b/application/project/controller/Task.php @@ -0,0 +1,420 @@ +model) { + $this->model = new \app\common\Model\Task(); + } + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $where = []; + $params = Request::only('stageCode,pcode,keyword,order,projectCode,deleted'); + foreach (['stageCode', 'pcode', 'deleted', 'projectCode'] as $key) { + if ($key == 'projectCode') { + (isset($params[$key]) && $params[$key] !== '') && $where[] = ['project_code', '=', $params[$key]]; + continue; + } + (isset($params[$key]) && $params[$key] !== '') && $where[] = [$key, '=', $params[$key]]; + } + if (isset($params['keyword'])) { + $where[] = ['name', 'like', "%{$params['keyword']}%"]; + } + $order = 'sort asc,id asc'; + if (isset($params['order'])) { + $order = $params['order']; + } + $list = $this->model->_list($where, $order); + if ($list['list']) { + foreach ($list['list'] as &$task) { + $task['executor'] = Member::where(['code' => $task['assign_to']])->field('name,avatar')->find(); + } + } + $this->success('', $list); + } + + /** + * 项目时间段任务统计 + */ + public function dateTotalForProject() + { + $projectCode = Request::post('projectCode'); + $list = $this->model->dateTotalForProject($projectCode); + $this->success('', $list); + } + + /** + * 获取自己的任务 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function selfList() + { + $type = Request::post('type'); + $member = getCurrentMember(); + $done = 1; + if (!$type) { + $done = 0; + } + $list = $this->model->getMemberTasks($member['code'], $done, Request::post('page'), Request::post('pageSize')); + if ($list['list']) { + foreach ($list['list'] as &$task) { + $task['executor'] = Member::where(['code' => $task['assign_to']])->field('name,avatar')->find(); + $task['projectInfo'] = \app\common\Model\Project::where(['code' => $task['project_code']])->field('name,code')->find(); + } + } + $this->success('', $list); + } + + public function taskSources() + { + $code = Request::post('taskCode'); + try { + $list = $this->model->taskSources($code); + } catch (\Exception $e) { + $this->error($e->getMessage()); + } + $this->success('', $list); + } + + public function read(Request $request) + { + $data = $request::only('taskCode'); + + try { + $result = $this->model->read($data['taskCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode()); + } + if ($result) { + $this->success('', $result); + } + } + + /** + * 新增 + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,stage_code,project_code,assign_to,pcode'); + if (!isset($data['assign_to'])) { + $data['assign_to'] = ''; + } + if (!isset($data['pcode'])) { + $data['pcode'] = ''; + } + if (!$request::post('name')) { + $this->error("请填写任务标题"); + } + try { + $member = getCurrentMember(); + if ($data['pcode']) { + $parentTask = $this->model->where(['code' => $data['pcode']])->find(); + if (!$parentTask) { + throw new \Exception('父任务无效', 5); + } + if ($parentTask['deleted']) { + throw new \Exception('父任务在回收站中无法编辑', 6); + } + $result = $this->model->createTask($parentTask['stage_code'], $parentTask['project_code'], $data['name'], $member['code'], $data['assign_to'], $data['pcode']); + } else { + $result = $this->model->createTask($data['stage_code'], $data['project_code'], $data['name'], $member['code'], $data['assign_to']); + } + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success(); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 执行任务 + * @param Request $request + */ + public function taskDone(Request $request) + { + $data = $request::only('taskCode,done'); + if (!$request::post('taskCode')) { + $this->error("请选择任务"); + } + try { + $result = $this->model->taskDone($data['taskCode'], $data['done']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 指派任务 + * @param Request $request + */ + public function assignTask(Request $request) + { + $data = $request::only('taskCode,executorCode'); + if (!$request::post('taskCode')) { + $this->error("请选择任务"); + } + try { + $result = $this->model->assignTask($data['taskCode'], $data['executorCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 排序 + * @param Request $request + */ + public function sort(Request $request) + { + $data = $request::only('stageCode,codes'); + if (!$request::post('codes')) { + $this->error("参数有误"); + } + try { + $this->model->sort($data['stageCode'], explode(',', $data['codes'])); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(); + } + + public function createComment(Request $request) + { + $data = $request::only('taskCode,comment'); + if (!$request::post('taskCode')) { + $this->error("请选择任务"); + } + try { + $result = $this->model->createComment($data['taskCode'], $data['comment']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 保存 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit(Request $request) + { + $data = $request::only('name,sort,end_time,pri,description'); + $code = $request::post('taskCode'); + if (!$code) { + $this->error("请选择一个任务"); + } + $template = $this->model->where(['code' => $code])->field('id')->find(); + if (!$template) { + $this->error("该任务已失效"); + } + try { + $result = $this->model->edit($code, $data); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + + } + if ($result) { + $this->success(); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 点赞 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function like(Request $request) + { + $data = $request::only('like'); + $code = $request::post('taskCode'); + if (!$code) { + $this->error("请选择一个任务"); + } + $template = $this->model->where(['code' => $code])->field('id')->find(); + if (!$template) { + $this->error("该任务已失效"); + } + try { + $result = $this->model->like($code, $data['like']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + + } + if ($result) { + $this->success(); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 收藏 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function star(Request $request) + { + $data = $request::only('star'); + $code = $request::post('taskCode'); + if (!$code) { + $this->error("请选择一个任务"); + } + $task = $this->model->where(['code' => $code])->field('id')->find(); + if (!$task) { + $this->notFound(); + } + try { + $result = $this->model->star($code, $data['star']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + + } + if ($result) { + $this->success(); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * @throws \think\exception\DbException + */ + public function taskLog() + { + $taskCode = Request::post('taskCode'); + $showAll = Request::post('all', 0); + $onlyComment = Request::post('comment', 0); + $where = []; + $where[] = ['source_code', '=', $taskCode]; + $where[] = ['action_type', '=', 'task']; + if ($onlyComment) { + $where[] = ['is_comment', '=', 1]; + } + $projectLogModel = new ProjectLog(); + if ($showAll) { + $list = []; + $list['list'] = $projectLogModel->where($where)->order('id asc')->select()->toArray(); + $list['total'] = count($list['list']); + } else { + $list = $projectLogModel->_list($where, 'id desc'); + if ($list['list']) { + $list['list'] = array_reverse($list['list']); + } + } + if ($list['list']) { + foreach ($list['list'] as &$item) { + $member = Member::where(['code' => $item['member_code']])->field('id,name,avatar,code')->find(); + !$member && $member = []; + $item['member'] = $member; + } + } + $this->success('', $list); + } + /** + * 批量放入回收站 + */ + public function recycleBatch() + { + try { + $this->model->recycleBatch(Request::post('stageCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 放入回收站 + */ + public function recycle() + { + try { + $this->model->recycle(Request::post('taskCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 恢复 + */ + public function recovery() + { + try { + $this->model->recovery(Request::post('taskCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 彻底删除 + */ + public function delete() + { + try { + $this->model->del(Request::post('taskCode')); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } +} diff --git a/application/project/controller/TaskMember.php b/application/project/controller/TaskMember.php new file mode 100644 index 0000000..73be49d --- /dev/null +++ b/application/project/controller/TaskMember.php @@ -0,0 +1,137 @@ +model) { + $this->model = new \app\common\Model\TaskMember(); + } + } + + /** + * 任务成员 + * @throws \think\exception\DbException + */ + public function index() + { + $taskCode = Request::post('taskCode'); + $where = []; + $where[] = ['task_code', '=', $taskCode]; + $list = $this->model->_list($where,'is_owner desc'); + if ($list['list']) { + foreach ($list['list'] as &$item) { + $member = Member::where(['code' => $item['member_code']])->field('id,name,avatar,code')->find(); + !$member && $member = []; + $member['is_executor'] = $item['is_executor']; + $member['is_owner'] = $item['is_owner']; + $item = $member; + } + } + $this->success('', $list); + } + + + /** + * 邀请成员查询 + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function searchInviteMember() + { + $keyword = trim(Request::post('keyword')); + $code = trim(Request::post('projectCode')); + if (!$code) { + $this->error('请先选择项目'); + } + $orgCode = getCurrentOrganizationCode(); + if (!$keyword) { + $this->success(''); + } + $project = \app\common\Model\Project::where(['code' => $code])->field('id')->find(); + $projectId = $project['id']; + //先找出项目所有成员 + $projectMemberIds = $this->model->where([['project_code', '=', $code]])->column('member_code'); + $tempList = []; + //从当前组织的所有成员查询,判断是否已加入该项目,并存储已加入项目的成员的account_id + $memberAccountList = MemberAccount::where([['name', 'like', "%{$keyword}%"], ['organization_code', '=', $orgCode]])->select()->toArray(); + if ($memberAccountList) { + foreach ($memberAccountList as $member) { + $item = []; + $item['memberCode'] = $member['member_code']; + $item['avatar'] = $member['avatar']; + $item['name'] = $member['name']; + $item['email'] = $member['email'] ?? '未绑定邮箱'; + $item['joined'] = false; + if (in_array($member['member_code'], $projectMemberIds)) { + $item['joined'] = true; + } + $projectMemberIds[] = $member['member_code']; + $tempList[$item['memberCode']] = $item; //为了去重 + } + } + //从平台查询 + $memberList = Member::where([['email', 'like', "%{$keyword}%"]])->whereNotIn('id', $projectMemberIds)->select()->toArray(); + if ($memberList) { + foreach ($memberList as $member) { + $item = []; + $item['memberCode'] = $member['member_code']; + $item['avatar'] = $member['avatar']; + $item['name'] = $member['name']; + $item['email'] = $member['email'] ?? '未绑定邮箱'; + $item['joined'] = false; + if (in_array($item['memberCode'], $projectMemberIds)) { + $item['joined'] = true; + } + $tempList[$item['memberCode']] = $item; //为了去重 + } + } + $this->success('', array_values($tempList));//数组下标重置 + } + + /** + * 邀请成员 + */ + public function inviteMember() + { + $data = Request::only('memberCode,taskCode'); + if (!$data['memberCode'] || !$data['taskCode']) { + $this->error('数据异常'); + } + try { + $this->model->inviteMember($data['memberCode'], $data['taskCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } + + /** + * 批量邀请成员 + */ + public function inviteMemberBatch() + { + $data = Request::only('memberCodes,taskCode'); + if (!$data['memberCodes'] || !$data['taskCode']) { + $this->error('数据异常'); + } + try { + $this->model->inviteMemberBatch(json_decode($data['memberCodes']), $data['taskCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(''); + } +} diff --git a/application/project/controller/TaskStages.php b/application/project/controller/TaskStages.php new file mode 100644 index 0000000..18d5454 --- /dev/null +++ b/application/project/controller/TaskStages.php @@ -0,0 +1,161 @@ +model) { + $this->model = new \app\common\Model\TaskStages(); + } + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $where = []; + $code = Request::post('projectCode'); + if (!$code) { + $this->error("请选择一个项目"); + } + $where[] = ['project_code', '=', $code]; + $list = $this->model->_list($where, 'sort asc,id asc'); + if ($list['list']) { + foreach ($list['list'] as &$item) { + unset($item['id']); + $item['tasksLoading'] = true; //任务加载状态 + $item['fixedCreator'] = false; //添加任务按钮定位 + $item['showTaskCard'] = false; //是否显示创建卡片 + $item['tasks'] = []; + } + } + $this->success('', $list); + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function tasks() + { + $where = []; + $code = Request::post('stageCode'); + if (!$code) { + $this->error("数据解析异常"); + } + $where[] = ['stage_code', '=', $code]; + $list = $this->model->tasks($code); +// $list = \app\common\Model\Task::alias('t')->join('member m','t.assign_to = m.code')->field()->where(['stage_code'=>$code])->select(); + + $this->success('', $list); + } + + public function sort(Request $request) + { + $data = $request::only('preCode,nextCode'); + if (!$request::post('preCode') || !$request::post('nextCode')) { + $this->error("参数有误"); + } + try { + $this->model->sort($data['preCode'], $data['nextCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + $this->success(); + } + + /** + * 新增 + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,projectCode'); + if (!$request::post('name')) { + $this->error("请填写列表名称"); + } + try { + $result = $this->model->createStage($data['name'], $data['projectCode']); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('添加成功', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 保存 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit(Request $request) + { + $data = $request::only('name,stageCode'); + if (!$request::post('name')) { + $this->error("请填写列表名称"); + } + if (!$data['stageCode']) { + $this->error("请选择一个列表"); + } + $template = $this->model->where(['code' => $data['stageCode']])->field('id')->find(); + if (!$template) { + $this->error("该列表已失效"); + } + $result = $this->model->_edit(['name' => $data['name']], ['code' => $data['stageCode']]); + if ($result) { + $this->success(''); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除列表 + * @return void + */ + public function delete() + { + $code = Request::post('code'); + if (!$code) { + $this->error("请选择一个列表"); + } + try { + $result = $this->model->deleteStage($code); + } catch (\Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success(''); + } + } +} diff --git a/application/project/controller/TaskStagesTemplate.php b/application/project/controller/TaskStagesTemplate.php new file mode 100644 index 0000000..b890539 --- /dev/null +++ b/application/project/controller/TaskStagesTemplate.php @@ -0,0 +1,114 @@ +model) { + $this->model = new \app\common\Model\TaskStagesTemplate(); + } + } + + /** + * 显示资源列表 + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $where = []; + $code = Request::post('code'); + if (!$code) { + $this->error("请选择一个项目"); + } + $where[] = ['project_template_code', '=', $code]; + $list = $this->model->_list($where,'sort desc,id asc'); + $this->success('', $list); + } + + /** + * 新增 + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,template_code,sort'); + if (!$request::post('name')) { + $this->error("请填写任务名称"); + } + try { + $result = $this->model->createTemplate($data['template_code'], $data['name'], $data['sort']); + } catch (Exception $e) { + $this->error($e->getMessage(), $e->getCode());; + } + if ($result) { + $this->success('添加成功', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 保存 + * @param Request $request + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function edit(Request $request) + { + $data = $request::only('name,sort'); + $code = $request::post('code'); + if (!$code) { + $this->error("请选择一个任务"); + } + $template = $this->model->where(['code' => $code])->field('id')->find(); + if (!$template) { + $this->error("该任务已失效"); + } + $result = $this->model->_edit($data, ['code' => $code]); + if ($result) { + $this->success('编辑任务成功'); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除任务 + * @return void + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function delete() + { + $code = Request::post('code'); + if (!$code) { + $this->error("请选择一个任务"); + } + $this->model->deleteTemplate($code); + $this->success(''); + } +} diff --git a/application/project/controller/project/Index.php b/application/project/controller/project/Index.php new file mode 100644 index 0000000..7fdd724 --- /dev/null +++ b/application/project/controller/project/Index.php @@ -0,0 +1,125 @@ +model) { + $this->model = new \app\common\Model\Project(); + } + } + + /** + * 显示资源列表 + * + * @return void + * @throws \think\exception\DbException + */ + public function index() + { + $where = []; + $params = Request::post(); + foreach (['name'] as $key) { + (isset($params[$key]) && $params[$key] !== '') && $where[] = [$key, 'like', "%{$params[$key]}%"]; + } + foreach (['state'] as $key) { + (isset($params[$key]) && $params[$key] !== '') && $where[] = [$key, '=', $params['state']]; + } + if (isset($params['date']) && $params['date'] !== '') { + list($start, $end) = explode('~', $params['date']); + $where[] = ['regdate', 'between', ["{$start} 00:00:00", "{$end} 23:59:59"]]; + } + $list = $this->model->_list($where); + $result = ['list' => $list]; + $this->success('', $result); + + + } + + /** + * 新增 + * + * @param Request $request + * @return void + */ + public function save(Request $request) + { + $data = $request::only('name,address,lng,lat,phone,type_id,open_time,close_time,areas'); + + list($data['province'], $data['city'], $data['area']) = json_decode($data['areas']); + unset($data['areas']); + + $data['landlord_id'] = $this->session['landlord_id']; + $data['status'] = 0; + if (empty($data['landlord_id'])) { + $this->success('', ''); + } + $result = $this->model->_add($data); + if ($result) { + $this->success('', $result); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 获取信息 + * + * @param Request $request + * @return void + * @throws \think\Exception\DbException + */ + public function read(Request $request) + { + $this->success('', $this->model->get($request::post('id'))); + } + + /** + * 保存 + * + * @param Request $request + * @return void + */ + public function edit(Request $request) + { + $data = $request::only('id,name,address,lng,lat,phone,type_id,open_time,close_time,areas'); + + list($data['province'], $data['city'], $data['area']) = json_decode($data['areas']); + unset($data['areas']); + + //编辑后重新审核 + $data['status'] = 0; + $result = $this->model->_edit($data); + if ($result) { + $this->success(''); + } + $this->error("操作失败,请稍候再试!"); + } + + /** + * 删除指定资源 + * + * @param int $id + * @return \think\Response + */ + public function delete($id = 0) + { + $this->model->destroy(Request::post('id')); + $this->success(''); + } + +} diff --git a/application/project/middleware.php b/application/project/middleware.php new file mode 100644 index 0000000..aa68447 --- /dev/null +++ b/application/project/middleware.php @@ -0,0 +1,6 @@ +header('Origin'); + $Headers = $request->header('Access-Control-Request-Headers'); + header('Access-Control-Allow-Origin: ' . $Origin); + header('Access-Control-Allow-Headers: ' . $Headers); + header('Access-Control-Allow-Credentials:true'); + header('Access-Control-Allow-Methods: GET, POST, OPTIONS, PUT, DELETE'); + header('Access-Control-Max-Age: 1728000'); + header('Content-Type:text/plain charset=UTF-8'); + + if (strtoupper($request->method()) == "OPTIONS") { + header('HTTP/1.0 204 No Content'); + header('Content-Length:0'); + header('status:204'); + exit(); + } + list($module, $controller, $action) = [$request->module(), $request->controller(), $request->action()]; + $access = $this->buildAuth($node = NodeService::parseNodeStr("{$module}/{$controller}/{$action}")); + $currentOrganizationCode = $request->header('organizationCode'); + if ($currentOrganizationCode) { + session('currentOrganizationCode', $currentOrganizationCode); + } +// var_dump(session_id()); +// die; + + // 登录状态检查 + if (!empty($access['is_login']) && !session('member')) { + $msg = ['code' => 401, 'msg' => '抱歉,您还没有登录获取访问权限!']; + return json($msg); + } + + // 访问权限检查 + if (!empty($access['is_auth']) && !auth($node, 'project')) { + return json(['code' => 403, 'msg' => '无权限操作资源,访问被拒绝']); + } + return $next($request); + } + + /** + * 根据节点获取对应权限配置 + * @param string $node 权限节点 + * @return array + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + private function buildAuth($node) + { + $info = ProjectNode::cache(true, 30)->where(['node' => $node])->find(); + return [ + 'is_menu' => intval(!empty($info['is_menu'])), + 'is_auth' => intval(!empty($info['is_auth'])), + 'is_login' => empty($info['is_auth']) ? intval(!empty($info['is_login'])) : 1, + ]; + } +} diff --git a/application/project/middleware/ProjectAuth.php b/application/project/middleware/ProjectAuth.php new file mode 100644 index 0000000..a21d154 --- /dev/null +++ b/application/project/middleware/ProjectAuth.php @@ -0,0 +1,143 @@ +module(), $request->controller(), $request->action()]; + $node = "$controller/$action"; + //方法转小写 + foreach ($this->needAuthActions as &$action) { + $arr = explode('/', $action); + $arr[1] = strtolower($arr[1]); + $action = implode('/', $arr); + } + //操作权限 + if (in_array($node, $this->needAuthActions)) { + $code = $this->getCode(); + if (!$code) { +// return json(['code' => 404, 'msg' => '资源不存在']); + } + if ($code) { + $result = $this->checkAuth($code); + if (!$result) { + return json(['code' => 403, 'msg' => '无权限操作资源,访问被拒绝']); + } + } + } + //只读权限 + if (in_array($node, $this->needVisibleActions)) { + $code = $this->getCode(); + if ($code) { + $info = Project::where(['code' => $code])->field('private')->find(); + if ($info['private']) { + $result = $this->checkAuth($code); + if (!$result) { + return json(['code' => 4031, 'msg' => '无权限操作资源,访问被拒绝']); + } + } + } + } + return $next($request); + } + + public function getCode() + { + $code = \think\facade\Request::param('projectCode'); + if (!$code) { + $code = \think\facade\Request::param('project_code'); + } + if (!$code) { + $taskCode = \think\facade\Request::param('taskCode'); + if (!$taskCode) { + $taskCode = \think\facade\Request::param('pcode'); // 父任务 + } + $task = Task::where(['code' => $taskCode])->field('project_code')->find(); + + if ($task) { + $code = $task['project_code']; + } + } + return $code; + } + + /** + * 检测操作权限 + * @param $code + * @return bool + * @throws \think\db\exception\DataNotFoundException + * @throws \think\db\exception\ModelNotFoundException + * @throws \think\exception\DbException + */ + public function checkAuth($code) + { + $info = Project::where(['code' => $code])->field('private')->find(); + if (!$info) { + return false; + } + $where = ['project_code' => $code, 'member_code' => getCurrentMember()['code']]; + $projectMember = ProjectMember::where($where)->field('id')->find(); + if (!$projectMember) { + return false; + } + return true; + } +} diff --git a/application/project/tags.php b/application/project/tags.php new file mode 100644 index 0000000..7a6879a --- /dev/null +++ b/application/project/tags.php @@ -0,0 +1,9 @@ + [ + 'app\\project\\behavior\\Task' + ], + 'project' => [ + 'app\\project\\behavior\\Project' + ], +]; diff --git a/build.cmd b/build.cmd new file mode 100644 index 0000000..c1e3c74 --- /dev/null +++ b/build.cmd @@ -0,0 +1,12 @@ +@echo off +title Composer Plugs Update and Optimize +echo. +echo ========= 1. Ѱװ ========= +@rmdir /s/q vendor thinkphp +echo. +echo ========= 2. زװ ========= +composer update --profile --prefer-dist --optimize-autoloader +echo. +echo ========= 3. ѹ ========= +composer dump-autoload --optimize +exit \ No newline at end of file diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..53ab9a6 --- /dev/null +++ b/build.sh @@ -0,0 +1,37 @@ +#!/bin/bash +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin +LANG=en_US.UTF-8 + +export PATH + +echo " ++---------------------------------------------------------------------- +| ThinkAdmin environmental preparation tools ++---------------------------------------------------------------------- +| GtiHub : https://github.com/zoujingli/ThinkAdmin ++---------------------------------------------------------------------- +| document : https://www.kancloud.cn/zoujingli/thinkadmin/323614 ++---------------------------------------------------------------------- +" + +hasComposer=`command -v composer` + +echo -e "\033[34mConfirm the existence of the command....\033[0m" + +if [ ! -f "${hasComposer}" ]; then +echo -e "\033[31mComposer Not Found! Initialization cannot continue. \033[0m" +exit +fi + +echo -e "\033[34mClean up the running environment....\033[0m" +rm -rf ./vendor +rm -rf ./thinkphp +rm -rf ./composer.lock + +echo -e "\033[34mComposer install....\033[0m" +composer install --profile --prefer-dist --optimize-autoloader + +echo -e "\033[34mMake Autoload....\033[0m" +composer dump-autoload --optimize + +echo -e "\033[31mEnvironmental preparation success!\033[0m" \ No newline at end of file diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..9ef26a3 --- /dev/null +++ b/composer.json @@ -0,0 +1,41 @@ +{ + "type": "pearProject", + "name": "pearProject", + "description": "pearProject", + "homepage": "https://beta.vilson.xyz", + "keywords": [ + "project", + "plan" + ], + "license": "MIT", + "authors": [ + { + "name": "vilson", + "email": "545522390@qq.com" + } + ], + "require": { + "php": ">=7.2", + "qiniu/php-sdk": "^7.2", + "endroid/qr-code": "^1.9", + "topthink/framework": "5.1.*", + "aliyuncs/oss-sdk-php": "^2.2", + "zoujingli/ip2region": "dev-master", + "topthink/think-image": "^1.0", + "workerman/gateway-worker-for-win" : ">=3.0.0", + "workerman/gateway-worker" : ">=3.0.0", + "overtrue/easy-sms": "^1.1", + "phpoffice/phpspreadsheet": "^1.5" + }, + "autoload": { + "psr-4": { + "app\\": "application" + } + }, + "extra": { + "think-path": "thinkphp" + }, + "config": { + "preferred-install": "dist" + } +} diff --git a/config/aliyun.php b/config/aliyun.php new file mode 100644 index 0000000..f3b85d6 --- /dev/null +++ b/config/aliyun.php @@ -0,0 +1,6 @@ + '', + 'SmsAppkey' => '', + 'SignName' => '', +]; diff --git a/config/app.php b/config/app.php new file mode 100644 index 0000000..dc97f11 --- /dev/null +++ b/config/app.php @@ -0,0 +1,136 @@ + 'pearProject', + // 应用版本 + 'app_version' => '2.0.0', + // 应用地址 + 'app_host' => '', + // 应用调试模式 + 'app_debug' => true, + // 应用Trace + 'app_trace' => false, + // 是否支持多模块 + 'app_multi_module' => true, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 默认模块名 + 'default_module' => 'index', + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 默认验证器 + 'default_validate' => '', + // 默认的空模块名 + 'empty_module' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 自动搜索控制器 + 'controller_auto_search' => false, + + // +---------------------------------------------------------------------- + // | URL设置 + // +---------------------------------------------------------------------- + + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'X-REAL-IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 1, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 使用注解路由 + 'route_annotation' => false, + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 + 'route_check_cache_key' => '', + // 路由缓存类型及参数 + 'route_cache_option' => '', + + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => Env::get('think_path') . 'tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => Env::get('think_path') . 'tpl/dispatch_jump.tpl', + + // 异常页面的模板文件 + 'exception_tmpl' => Env::get('think_path') . 'tpl/think_exception.tpl', + + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', +]; diff --git a/config/cache.php b/config/cache.php new file mode 100644 index 0000000..b0b9b2b --- /dev/null +++ b/config/cache.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +// +---------------------------------------------------------------------- +// | 缓存设置 +// +---------------------------------------------------------------------- + +/*return [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + 'path' => '', + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, +];*/ +return [ + 'type' => 'complex', + // 默认使用的缓存 + 'default' => [ + // 驱动方式 + 'type' => 'file', + // 缓存保存目录 + 'path' => './runtime/default', + ], + // redis缓存 + 'redis' => [ + // 驱动方式 + 'type' => 'redis', + // 服务器地址 + 'host' => '127.0.0.1', + // 端口 + 'port' => '6379', + // 缓存保存目录 + 'path' => './runtime/cache', + // 全局缓存有效期(0为永久有效) + 'expire'=> 0, + ], +]; diff --git a/config/console.php b/config/console.php new file mode 100644 index 0000000..dc39015 --- /dev/null +++ b/config/console.php @@ -0,0 +1,7 @@ + 'Think Console', + 'version' => '0.1', + 'user' => null, +]; diff --git a/config/const.php b/config/const.php new file mode 100644 index 0000000..5ff6dd3 --- /dev/null +++ b/config/const.php @@ -0,0 +1,4 @@ + 'Mcl5FwCI8cDtnBCQ', +]; diff --git a/config/cookie.php b/config/cookie.php new file mode 100644 index 0000000..8251c5f --- /dev/null +++ b/config/cookie.php @@ -0,0 +1,18 @@ + '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, +]; diff --git a/config/database.php b/config/database.php new file mode 100644 index 0000000..1af9907 --- /dev/null +++ b/config/database.php @@ -0,0 +1,55 @@ + 'mysql', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => 'pearproject', + // 用户名 + 'username' => 'root', + // 密码 + 'password' => 'root', + + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => 'pear_', + // 数据库调试模式 + 'debug' => true, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 自动读取主库数据 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], +]; diff --git a/config/log.php b/config/log.php new file mode 100644 index 0000000..5f5f0a9 --- /dev/null +++ b/config/log.php @@ -0,0 +1,18 @@ + 'File', + // 日志保存目录 + 'path' => '', + // 日志记录级别 + 'level' => [], + // 单文件日志写入 + 'single' => false, + // 独立日志级别 + 'apart_level' => ['emergency', 'alert', 'critical', 'error', 'sql','lock'], + // 最大日志文件数量 + 'max_files' => 0, + // 是否关闭日志写入 + 'close' => false, +]; diff --git a/config/session.php b/config/session.php new file mode 100644 index 0000000..1390224 --- /dev/null +++ b/config/session.php @@ -0,0 +1,20 @@ + '', + 'type' => '', + 'prefix' => 'ta', + 'auto_start' => true, + 'path' => $session_path, + 'name' => $session_name, + 'var_session_id' => $session_name, + +// 'use_trans_sid' => 0, +// 'httponly' => true, +// 'secure' => true, +]; diff --git a/config/sms.php b/config/sms.php new file mode 100644 index 0000000..5a8b2a9 --- /dev/null +++ b/config/sms.php @@ -0,0 +1,28 @@ + 5.0, + + // 默认发送配置 + 'default' => [ + // 网关调用策略,默认:顺序调用 + 'strategy' => \Overtrue\EasySms\Strategies\OrderStrategy::class, + + // 默认可用的发送网关 + 'gateways' => [ + 'submail' + ], + ], + // 可用的网关配置 + 'gateways' => [ + 'errorlog' => [ + 'file' => '/tmp/easy-sms.log', + ], + 'submail' => [ + 'app_id' => '', + 'app_key' => '', + 'project' => '', // 默认 project,可在发送时 data 中指定 + ], + ], +]; diff --git a/config/template.php b/config/template.php new file mode 100644 index 0000000..583a973 --- /dev/null +++ b/config/template.php @@ -0,0 +1,32 @@ +root(); +$uriRoot = rtrim(preg_match('/\.php$/', $appRoot) ? dirname($appRoot) : $appRoot, '\\/'); + +return [ + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 3 保持操作方法 + 'auto_rule' => 1, + // 模板路径 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + // 定义模板替换字符串 + 'tpl_replace_string' => [ + '__APP__' => $appRoot, + '__ROOT__' => $uriRoot, + '__STATIC__' => $uriRoot . "/static", + ], +]; diff --git a/config/trace.php b/config/trace.php new file mode 100644 index 0000000..c4cad62 --- /dev/null +++ b/config/trace.php @@ -0,0 +1,6 @@ + 'Html', +]; diff --git a/config/upload.php b/config/upload.php new file mode 100644 index 0000000..253e584 --- /dev/null +++ b/config/upload.php @@ -0,0 +1,12 @@ + 'static/upload/', + 'default' => 'default', + 'editor_img' => 'editor/img', + 'member_avatar' => 'member/avatar', + 'image' => 'image/default', + + 'file' => 'file/default', + 'file_temp' => 'file/temp', +]; diff --git a/config/wechat.php b/config/wechat.php new file mode 100644 index 0000000..0bd49a0 --- /dev/null +++ b/config/wechat.php @@ -0,0 +1,9 @@ + 'https://x.x.com', + // 下面参数用作微信支付 + 'appid' => '', + 'mch_id' => '', + 'mch_key' => '', +]; diff --git a/data/2.0.0/pearproject.sql b/data/2.0.0/pearproject.sql new file mode 100644 index 0000000..353a869 --- /dev/null +++ b/data/2.0.0/pearproject.sql @@ -0,0 +1,2568 @@ +/* + Navicat Premium Data Transfer + + Source Server : 本地 + Source Server Type : MySQL + Source Server Version : 50642 + Source Host : 127.0.0.1:3306 + Source Schema : pearproject + + Target Server Type : MySQL + Target Server Version : 50642 + File Encoding : 65001 + + Date: 16/01/2019 15:30:27 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for pear_build +-- ---------------------------- +DROP TABLE IF EXISTS `pear_build`; +CREATE TABLE `pear_build` ( + `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `product` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `branch` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `project` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `name` char(150) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `scmPath` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `filePath` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `date` date NOT NULL, + `stories` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `bugs` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `builder` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `desc` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `deleted` enum('0','1') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + INDEX `build`(`product`, `project`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 75 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '版本表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_build +-- ---------------------------- +INSERT INTO `pear_build` VALUES (73, 0, 0, 1292, '2.13.0 King of Opera', '', '', '2018-04-30', '', '', 'admin', '
  • Table 列新增属性 minWidth 和 maxWidth#3284
  • DatePicker 的 disabledDate 功能,现在也能限制时、分、秒了。#3246
  • 优化 Table 筛选样式。#3206
  • 修复 Table 在多级表头里使用过滤和排序的 bug。#3339
  • 修复 Table 在 2.12.0 版本,设置 show-header=\"false\" 报错的 bug。
  • 修复 Poptip 和 Tooltip 有时方向识别错误的 bug,并支持自定义 popper.js 的 options 选项。
  • 修复 DatePicker 在 daterange 模式下,选择年、月后显示值不正确的 bug。#3345
  • 修复 DatePicker 在 Safari 浏览器下 on-change 事件返回值有时不正确的 bug。#3232
  • 修复 DatePicker 的 show-week-numbers 无法动态设置的 bug。#3277
', '0'); +INSERT INTO `pear_build` VALUES (74, 0, 0, 1292, '2.13.1', '', '', '2018-04-30', '', '', 'admin', '
  • Tag 新增属性 fade
  • InputNumber 新增属性 placeholder#3424
  • InputNumber 的事件 on-focus 增加返回值 event。#3395
  • DatePicker 的事件 on-change 增加返回值 type。#3353
  • 优化 popper.js 的配置及 dropdown 的展开动画。#3354
  • 修复 Table 在动态调整页面宽度,有时滚动条显示错误的 bug。#3358
  • 修复 Poptip / Tooltip 动态修改内容后,位置计算不准确的 bug。#3412
  • 修复在 Form 内使用 Carousel 时,点击按钮会跳转的问题。#3426
', '0'); + +-- ---------------------------- +-- Table structure for pear_collection +-- ---------------------------- +DROP TABLE IF EXISTS `pear_collection`; +CREATE TABLE `pear_collection` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类型', + `source_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务ID', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 113 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '收藏表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_collection +-- ---------------------------- +INSERT INTO `pear_collection` VALUES (108, 'aut9wrz1pn0elf5s47ivx26o', 'task', 'aut9wrz1pn0elf5s47ivx26o', '6v7be19pwman2fird04gqu53', '2018-12-30 17:14:23'); +INSERT INTO `pear_collection` VALUES (109, 'mv4usefb06dxv8ez2spkl223', 'task', 'mv4usefb06dxv8ez2spkl223', '6v7be19pwman2fird04gqu53', '2018-12-30 22:16:05'); +INSERT INTO `pear_collection` VALUES (110, 'twb8f52jasn9vry6iko0dqg4', 'task', 'twb8f52jasn9vry6iko0dqg4', '6v7be19pwman2fird04gqu53', '2019-01-13 19:44:34'); +INSERT INTO `pear_collection` VALUES (112, 'na6uwxzi2fg3sre5qy9vb8m1', NULL, 'uwq87z2f0hnvrl6o9gtcb3iy', '6v7be19pwman2fird04gqu53', '2019-01-13 20:47:37'); + +-- ---------------------------- +-- Table structure for pear_department +-- ---------------------------- +DROP TABLE IF EXISTS `pear_department`; +CREATE TABLE `pear_department` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `organization_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织编号', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `pcode` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '上级编号', + `icon` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标', + `create_time` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `path` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '上级路径', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_department +-- ---------------------------- +INSERT INTO `pear_department` VALUES (1, '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '技术部', 0, '', NULL, NULL, NULL); +INSERT INTO `pear_department` VALUES (2, '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '开发1部', 0, '6v7be19pwman2fird04gqu53', NULL, NULL, '6v7be19pwman2fird04gqu53'); +INSERT INTO `pear_department` VALUES (3, 'pn6fyumbd9clz0t32kxr1qj8', 'bhlmq6n5edixkwct17a2gpv3', '太阳总部', 0, '', NULL, '2019-01-13 11:09:58', ''); +INSERT INTO `pear_department` VALUES (4, 'szdc5ojkgb802urvyxah196e', 'bhlmq6n5edixkwct17a2gpv3', '黑子1部', 0, 'pn6fyumbd9clz0t32kxr1qj8', NULL, '2019-01-13 11:10:24', 'pn6fyumbd9clz0t32kxr1qj8'); + +-- ---------------------------- +-- Table structure for pear_department_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_department_member`; +CREATE TABLE `pear_department_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT 'id', + `department_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '部门id', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织id', + `account_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `join_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加入时间', + `is_principal` tinyint(1) NULL DEFAULT NULL COMMENT '是否负责人', + `is_owner` tinyint(1) NULL DEFAULT 0 COMMENT '拥有者', + `authorize` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '部门-成员表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_department_member +-- ---------------------------- +INSERT INTO `pear_department_member` VALUES (34, 'fze7qr03v1dhtaygpjco9254', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu22', '2019-01-07 09:36:39', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (35, 'tjf432lxcuoizvhk95rgm78w', '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu22', '2019-01-07 09:37:08', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (36, '2tyvcl53bdr1a9h4ofxuepg8', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu55', '2019-01-07 09:48:37', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (37, 'gd6f5a8qmzor239kvlixn071', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu11', '2019-01-07 09:49:03', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (38, 'uzodyahgnc5pqk1iv2sef86x', '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu55', '2019-01-07 09:52:29', 0, 0, NULL); + +-- ---------------------------- +-- Table structure for pear_file +-- ---------------------------- +DROP TABLE IF EXISTS `pear_file`; +CREATE TABLE `pear_file` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '编号', + `path_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '相对路径', + `title` char(90) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `extension` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '扩展名', + `size` mediumint(8) UNSIGNED NULL DEFAULT 0 COMMENT '文件大小', + `object_type` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '对象类型', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织编码', + `task_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务编码', + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '项目编码', + `create_by` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '上传人', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `downloads` mediumint(8) UNSIGNED NULL DEFAULT 0 COMMENT '下载次数', + `extra` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '额外信息', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '删除标记', + `file_url` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '完整地址', + `file_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型', + `deleted_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 44 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_file +-- ---------------------------- +INSERT INTO `pear_file` VALUES (34, 'lhp9dfz831jquoam6g4nbery', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-ioncube_loaders_win_nonts_vc11_x86.zip', 'ioncube_loaders_win_nonts_vc11_x86', 'zip', 793854, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-ioncube_loaders_win_nonts_vc11_x86.zip', 'application/x-zip-compressed', ''); +INSERT INTO `pear_file` VALUES (35, 'lr08qzj5bucy2p1osinhkdef', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-技术部项目周报.xlsx', '技术部项目周报', 'xlsx', 8, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-技术部项目周报.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ''); +INSERT INTO `pear_file` VALUES (36, 'gf1zm573upka8htwlydsjxcr', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104607-cover.png', 'cover', 'png', 99467, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-11 10:46:07', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104607-cover.png', 'image/png', ''); +INSERT INTO `pear_file` VALUES (37, '0vr64cpylg3sbhkuij8a2en5', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-2.jpg', '2', 'jpg', 176083, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:05', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-2.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (38, 'qr18x4eja9vs35ftudck7w2m', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-1.jpg', '1', 'jpg', 157751, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:05', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-1.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (39, 'txu5z7rg6bavnk4h3y8wq9i2', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-3.jpg', '3', 'jpg', 150189, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:05', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-3.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (40, 'dqkx4o6wp2r9uzt15fyaenlv', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184741-5a1d10000fc8c.jpg', '5a1d10000fc8c', 'jpg', 445137, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:41', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184741-5a1d10000fc8c.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (42, '7ru54lhm6i198stqkdcy3ap2', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184757-05990022176026337.jpg', '05990022176026337', 'jpg', 45930, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:57', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184757-05990022176026337.jpg', 'image/jpeg', '2019-01-12 22:26:56'); +INSERT INTO `pear_file` VALUES (43, 'tfydkno68i4b7ha0q1x2uwcs', 'static/upload/file/default/bh5mdpzy7wg46kiqx9uclns2/6v7be19pwman2fird04gqu53/20190113/20190113122337-avatar.png', 'avatar', 'png', 51574, '', 'bh5mdpzy7wg46kiqx9uclns2', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-13 12:23:37', 0, '', 0, 'http://easyproject.net/static/upload/file/default/bh5mdpzy7wg46kiqx9uclns2/6v7be19pwman2fird04gqu53/20190113/20190113122337-avatar.png', 'image/png', ''); + +-- ---------------------------- +-- Table structure for pear_lock +-- ---------------------------- +DROP TABLE IF EXISTS `pear_lock`; +CREATE TABLE `pear_lock` ( + `pid` bigint(20) UNSIGNED NOT NULL COMMENT 'IP+TYPE', + `pvalue` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '次数', + `expiretime` int(11) NOT NULL DEFAULT 0 COMMENT '锁定截止时间', + PRIMARY KEY (`pid`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '防灌水表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_lock +-- ---------------------------- +INSERT INTO `pear_lock` VALUES (21307064333, 2, 1475226020); + +-- ---------------------------- +-- Table structure for pear_mailqueue +-- ---------------------------- +DROP TABLE IF EXISTS `pear_mailqueue`; +CREATE TABLE `pear_mailqueue` ( + `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `toList` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `ccList` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `subject` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `body` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `addedBy` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `addedDate` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `sendTime` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'wait', + `failReason` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `sendTime`(`sendTime`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 31858 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '邮件队列' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for pear_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_member`; +CREATE TABLE `pear_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '系统前台用户表', + `account` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户登陆账号', + `password` char(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '登陆密码,32位加密串', + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户昵称', + `mobile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机', + `realname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '真实姓名', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态', + `last_login_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上次登录时间', + `sex` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '性别', + `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '头像', + `idcard` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证', + `province` int(11) NULL DEFAULT 0 COMMENT '省', + `city` int(11) NULL DEFAULT 0 COMMENT '市', + `area` int(11) NULL DEFAULT 0 COMMENT '区', + `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所在地址', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `code` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `username`(`account`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 589 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_member +-- ---------------------------- +INSERT INTO `pear_member` VALUES (586, 'Alians', 'e10adc3949ba59abbe56e057f20f883e', 'Alians', '18377893857', 'vilson', NULL, 1, '2019-01-04 21:23:23', '', 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', NULL, 0, 0, 0, NULL, NULL, 'vilson@qq.com', 'kqdcn2w40p58r31zyo6efjib'); +INSERT INTO `pear_member` VALUES (582, '123456', 'e10adc3949ba59abbe56e057f20f883e', 'vilson', '18681140825', 'juli', NULL, 1, '2019-01-16 14:32:39', '', 'https://static.vilson.xyz/cover.png', '', 0, 0, 0, NULL, NULL, '545522390@qq.com', '6v7be19pwman2fird04gqu53'); +INSERT INTO `pear_member` VALUES (587, 'Chihiro', 'e10adc3949ba59abbe56e057f20f883e', 'Chihiro', '18278881051', 'Chihiro', NULL, 1, '2019-01-04 21:28:53', '', 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', NULL, 0, 0, 0, NULL, NULL, '741648282@qq.com', 'y680trgedcavbhnz24u7i5m3'); +INSERT INTO `pear_member` VALUES (588, 'Json', 'f9f02f39d6d2048d760d8add98265ba1', 'Json', '18681140821', 'Json', '2019-01-05 21:57:01', 1, '2019-01-06 08:21:42', '', 'https://static.vilson.xyz/cover.png', NULL, 0, 0, 0, NULL, NULL, '123456@qq.com', 'vys8gd32cfui6brtwzj4pqho'); + +-- ---------------------------- +-- Table structure for pear_member_account +-- ---------------------------- +DROP TABLE IF EXISTS `pear_member_account`; +CREATE TABLE `pear_member_account` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `member_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '所属账号id', + `organization_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '所属组织', + `department_code` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门编号', + `authorize` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色', + `is_owner` tinyint(1) NULL DEFAULT 0 COMMENT '是否主账号', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名', + `mobile` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号码', + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮件', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `last_login_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上次登录时间', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '职位', + `department` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '组织账号表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_member_account +-- ---------------------------- +INSERT INTO `pear_member_account` VALUES (21, '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '4', 1, 'vilson', '18681140825', '545522390@qq.com', NULL, NULL, 1, NULL, 'http://easyproject.net/static/upload/member/avatar/20181221/c3438c50fbc1b19607949893b42abee8.png', '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (22, '6v7be19pwman2fird04gqu22', 'kqdcn2w40p58r31zyo6efjib', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53,6v7be19pwman2fird04gqu11', '4', 0, 'Alians', '18377893857', 'vilson@qq.com', NULL, NULL, 1, 'eee', 'http://easyproject.net/static/upload/member/avatar/20181220/b417990ee8b131c2467ceda3e93ea8cf.jpg', NULL, NULL); +INSERT INTO `pear_member_account` VALUES (28, '0n52te9psyukd1g84frajwzv', '6v7be19pwman2fird04gqu53', 'bh5mdpzy7wg46kiqx9uclns2', '', NULL, 1, 'vilson', NULL, '545522390@qq.com', '2019-01-13 10:24:47', NULL, 1, NULL, 'https://static.vilson.xyz/cover.png', '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (24, '6v7be19pwman2fird04gqu44', 'kqdcn2w40p58r31zyo6efjib', '6v7be19pwman2fird04gqsss', '', NULL, 1, 'Alians', '18377893857', 'vilson@qq.com', NULL, NULL, 1, NULL, 'http://easyproject.net/static/upload/member/avatar/20181220/b417990ee8b131c2467ceda3e93ea8cf.jpg', NULL, NULL); +INSERT INTO `pear_member_account` VALUES (25, '6v7be19pwman2fird04gqu55', 'y680trgedcavbhnz24u7i5m3', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53,6v7be19pwman2fird04gqu11', '4', 0, 'Chihiro', '18278881051', '741648282@qq.com', NULL, NULL, 1, 'eee', 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', NULL, NULL); +INSERT INTO `pear_member_account` VALUES (26, '6v7be19pwman2fird04gqu66', 'vys8gd32cfui6brtwzj4pqho', '4ni58wts2egcybvodfh1kmaj', '', NULL, 1, 'Json', NULL, '123456@qq.com', '2019-01-05 21:57:01', NULL, 1, NULL, NULL, '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (30, 'if34h2lvdu06twxce1npmog7', 'vys8gd32cfui6brtwzj4pqho', '6v7be19pwman2fird04gqu53', '', '4', 0, 'Json', NULL, '123456@qq.com', '2019-01-16 15:18:29', NULL, 1, NULL, NULL, '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (29, 'vg10jpez6w4odt87mnyfhax5', '6v7be19pwman2fird04gqu53', 'bhlmq6n5edixkwct17a2gpv3', '', NULL, 1, 'vilson', NULL, '545522390@qq.com', '2019-01-13 10:26:44', NULL, 1, NULL, 'https://static.vilson.xyz/cover.png', '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); + +-- ---------------------------- +-- Table structure for pear_notify +-- ---------------------------- +DROP TABLE IF EXISTS `pear_notify`; +CREATE TABLE `pear_notify` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题', + `content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '内容', + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '通知类型。通知:notice,消息:message,待办:task', + `from` int(11) NULL DEFAULT 0 COMMENT '发送人id', + `to` int(11) NULL DEFAULT 0 COMMENT '送达用户id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生成时间', + `is_read` tinyint(1) NULL DEFAULT 0 COMMENT '是否已读', + `read_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '阅读时间', + `send_data` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '关联数据', + `finally_send_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最终发送时间', + `send_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发送时间', + `action` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'none' COMMENT '场景', + `terminal` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '推送终端。管理端:admin,移动端:wap', + `from_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '\'landord\',\'admin\',\'system\'', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 4244 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '动态通知表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for pear_organization +-- ---------------------------- +DROP TABLE IF EXISTS `pear_organization`; +CREATE TABLE `pear_organization` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `owner_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拥有者', + `create_time` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `personal` tinyint(1) NULL DEFAULT 0 COMMENT '是否个人项目', + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '编号', + `address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址', + `province` int(10) NULL DEFAULT 0 COMMENT '省', + `city` int(10) NULL DEFAULT 0 COMMENT '市', + `area` int(10) NULL DEFAULT 0 COMMENT '区', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '组织表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_organization +-- ---------------------------- +INSERT INTO `pear_organization` VALUES (1, 'vilson的个人项目', NULL, NULL, '6v7be19pwman2fird04gqu53', '2018-10-12', 1, '6v7be19pwman2fird04gqu53', NULL, 0, 0, 0); +INSERT INTO `pear_organization` VALUES (3, 'Alians的个人项目', NULL, NULL, 'kqdcn2w40p58r31zyo6efjib', '2018-10-12', 1, '6v7be19pwman2fird04gqsss', NULL, 0, 0, 0); +INSERT INTO `pear_organization` VALUES (4, 'Json的个人项目', NULL, NULL, 'vys8gd32cfui6brtwzj4pqho', '2019-01-05 21:57:01', 1, '4ni58wts2egcybvodfh1kmaj', NULL, 0, 0, 0); +INSERT INTO `pear_organization` VALUES (5, '星星联盟', NULL, NULL, '6v7be19pwman2fird04gqu53', '2019-01-13 10:24:42', 1, 'bh5mdpzy7wg46kiqx9uclns2', '星星联盟', 150000, 150300, 150303); +INSERT INTO `pear_organization` VALUES (6, '太阳联盟', NULL, NULL, '6v7be19pwman2fird04gqu53', '2019-01-13 10:26:39', 1, 'bhlmq6n5edixkwct17a2gpv3', '太阳联盟', 140000, 140300, 140303); + +-- ---------------------------- +-- Table structure for pear_project +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project`; +CREATE TABLE `pear_project` ( + `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `cover` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '封面', + `name` varchar(90) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `code` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编号', + `description` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '描述', + `access_control_type` enum('open','private','custom') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'open' COMMENT '访问控制l类型', + `white_list` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '可以访问项目的权限组(白名单)', + `order` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '排序', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '删除标记', + `template_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '项目类型', + `schedule` double(5, 2) NULL DEFAULT 0.00 COMMENT '进度', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织id', + `deleted_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除时间', + `private` tinyint(1) NULL DEFAULT 1 COMMENT '是否私有', + `prefix` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '项目前缀', + `open_prefix` tinyint(1) NULL DEFAULT 0 COMMENT '是否开启项目前缀', + `archive` tinyint(1) NULL DEFAULT 0 COMMENT '是否归档', + `archive_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '归档时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `project`(`order`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 13043 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_project +-- ---------------------------- +INSERT INTO `pear_project` VALUES (1, 'https://beta.vilson.xyz/static/upload//20190103/f9ad4e304ea0be7609e3236188f7547d.png', 'iView', 'a8mpr6tvbndk10hj2lwcqzuo', '那是一种内在的东西, 他们到达不了,也无法触及的', 'private', NULL, NULL, 0, '10', 39.00, '2018-04-30 22:29:18', '6v7be19pwman2fird04gqu53', NULL, 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (2, 'https://beta.vilson.xyz/static/upload//20190103/aaacec0e2001580b44dffbb967804349.png', 'Alipay', '8rlqyh56smzpoc1wef7390t2', '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', 'open', NULL, NULL, 0, '10', 75.00, '2018-05-01 09:28:36', '6v7be19pwman2fird04gqu53', NULL, 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (3, 'https://beta.vilson.xyz/static/upload//20190103/9ba2134d72cc3cec58f61024b89eb798.png', 'Vue', 'nkp4gulsb6oxqyi80fhead39', '生命就像一盒巧克力,结果往往出人意料', 'open', NULL, NULL, 0, '10', 63.00, '2018-05-01 09:33:43', '6v7be19pwman2fird04gqu53', '2019-01-03 22:20:10', 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (4, 'https://beta.vilson.xyz/static/upload//20190103/6fc14133651ee1c6ee1abaafcea76d01.png', 'Angular', 'sbklfvyouc0qpmwhitn47j5z', '希望是一个好东西,也许是最好的,好东西是不会消亡的', 'private', NULL, NULL, 0, '13', 100.00, '2018-05-01 09:36:05', '6v7be19pwman2fird04gqu53', NULL, 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (5, 'https://beta.vilson.xyz/static/upload//20190103/5d2a6e2d2cb235bb6888b884331bb516.png', 'EasyUI', 'n5opgqevrz1l03h48uwx67d2', '那时候我只会想自己想要什么,从不想自己拥有什么', 'open', NULL, NULL, 1, '0', 0.00, '2018-12-22 10:52:25', '6v7be19pwman2fird04gqu53', '2019-01-03 22:19:50', 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (1304, 'https://beta.vilson.xyz/static/upload//20190103/f5187655ceab8b52a335443664dffb3c.png', 'Vant', 'tnxpbov8kez6m4wl2hfjucd9', '现在的魏无羡,离开了蓝忘机就不行', 'open', NULL, 0, 0, '0', 50.00, '2018-12-23 08:31:53', '6v7be19pwman2fird04gqu53', '2019-01-04 11:33:02', 1, '', 0, 1, '2019-01-13 13:53:42'); +INSERT INTO `pear_project` VALUES (1303, 'https://beta.vilson.xyz/static/upload//20190103/30bdd62b610f5a4e3f788ec37e6c4a5b.png', 'Material UI', 'elqa703jyvfhpt1dsxkzi8on', '这个项目你不是项目成员,将不能进行操作(只读)', 'open', NULL, 0, 0, '0', 35.00, '2018-12-23 09:33:46', '6v7be19pwman2fird04gqu53', NULL, 0, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (1302, 'https://beta.vilson.xyz/static/upload//20190103/271ec382566f0d2ca187740330b19a17.png', 'Ant Motion', 'ibag9hw3o1tusd5qlpxrk782', '如果我真的存在,也是因为你需要我', 'open', NULL, 0, 1, '0', 50.00, '2018-12-23 09:53:25', '6v7be19pwman2fird04gqu53', '2019-01-04 21:48:33', 0, '', 0, 0, '2019-01-02 21:01:12'); +INSERT INTO `pear_project` VALUES (1305, 'https://beta.vilson.xyz/static/upload//20190103/d86b104c0e1131b2fbd06dce615470df.png', 'Ant Design', 'mo4uqwfb06dxv8ez2spkl3rg', '那时候我只会想自己想要什么,从不想自己拥有什么', 'open', NULL, 0, 0, '0', 24.00, '2018-12-25 07:20:36', '6v7be19pwman2fird04gqu53', '2019-01-02 22:06:02', 1, 'EP', 0, 0, '2019-01-02 20:59:09'); +INSERT INTO `pear_project` VALUES (1307, 'https://beta.vilson.xyz/static/upload//20190103/271ec382566f0d2ca187740330b19a17.png', '测试', '8ulzfth64cd0k1x5peivowm2', '测试11', 'open', NULL, 0, 1, '', 0.00, '2019-01-03 09:15:11', '6v7be19pwman2fird04gqu53', '2019-01-03 22:18:30', 1, '', 0, 0, '2019-01-03 10:52:54'); +INSERT INTO `pear_project` VALUES (13042, 'http://easyproject.net/static/image/default/project-cover.png', 'OKR 管理', 'gbim9jpevkh7qr6ufa1t3wl4', 'OKR 管理', 'open', NULL, 0, 1, '', 0.00, '2019-01-05 21:57:31', '4ni58wts2egcybvodfh1kmaj', '2019-01-06 08:21:49', 1, NULL, 0, 0, NULL); + +-- ---------------------------- +-- Table structure for pear_project_auth +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_auth`; +CREATE TABLE `pear_project_auth` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `title` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称', + `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态(1:禁用,2:启用)', + `sort` smallint(6) UNSIGNED NULL DEFAULT 0 COMMENT '排序权重', + `desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注说明', + `create_by` bigint(11) UNSIGNED NULL DEFAULT 0 COMMENT '创建人', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '所属组织', + `is_default` tinyint(1) NULL DEFAULT 0 COMMENT '是否默认', + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限类型', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目权限表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_auth +-- ---------------------------- +INSERT INTO `pear_project_auth` VALUES (1, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', '', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (2, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', '', 1, 'member'); +INSERT INTO `pear_project_auth` VALUES (3, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', '6v7be19pwman2fird04gqu53', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (4, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', '6v7be19pwman2fird04gqu53', 1, 'member'); +INSERT INTO `pear_project_auth` VALUES (6, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', 'bh5mdpzy7wg46kiqx9uclns2', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (7, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', 'bh5mdpzy7wg46kiqx9uclns2', 1, 'member'); +INSERT INTO `pear_project_auth` VALUES (8, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', 'bhlmq6n5edixkwct17a2gpv3', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (9, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', 'bhlmq6n5edixkwct17a2gpv3', 1, 'member'); + +-- ---------------------------- +-- Table structure for pear_project_auth_node +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_auth_node`; +CREATE TABLE `pear_project_auth_node` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `auth` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '角色ID', + `node` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '节点路径', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_system_auth_auth`(`auth`) USING BTREE, + INDEX `index_system_auth_node`(`node`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4192 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目角色与节点绑定' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_auth_node +-- ---------------------------- +INSERT INTO `pear_project_auth_node` VALUES (3097, 1, 'project'); +INSERT INTO `pear_project_auth_node` VALUES (3098, 1, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (3099, 1, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3100, 1, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3101, 1, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (3102, 1, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3103, 1, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (3104, 1, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3105, 1, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3106, 1, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3107, 1, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3108, 1, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (3109, 1, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (3110, 1, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3111, 1, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3112, 1, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3113, 1, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (3114, 1, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (3115, 1, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (3116, 1, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3117, 1, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3118, 1, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (3119, 1, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3120, 1, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3121, 1, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (3122, 1, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3123, 1, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3124, 1, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3125, 1, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (3126, 1, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3127, 1, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3128, 1, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3129, 1, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3130, 1, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3131, 1, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3132, 1, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3133, 1, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3134, 1, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3135, 1, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3136, 1, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3137, 1, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (3138, 1, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (3139, 1, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (3140, 1, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (3141, 1, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (3142, 1, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (3143, 1, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3144, 1, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3145, 1, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (3146, 1, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (3147, 1, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3148, 1, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3149, 1, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3150, 1, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3151, 1, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3152, 1, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3153, 1, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3154, 1, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3155, 1, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3156, 1, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3157, 1, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3158, 1, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3159, 1, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3160, 1, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (3161, 1, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3162, 1, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3163, 1, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3164, 1, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3165, 1, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3166, 1, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3167, 1, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3168, 1, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3169, 1, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (3170, 1, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (3171, 1, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (3172, 1, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3173, 1, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3174, 1, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (3175, 1, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3176, 1, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3177, 1, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3178, 1, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (3179, 1, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3180, 1, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3181, 1, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3182, 1, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3183, 1, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3184, 1, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (3185, 1, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3186, 1, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3187, 1, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3188, 1, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3189, 1, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3190, 1, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3191, 1, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3192, 1, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3193, 1, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3194, 1, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3195, 1, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3196, 1, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3197, 1, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3198, 1, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3199, 1, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3200, 1, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3201, 1, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3202, 1, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (3203, 1, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3204, 1, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3205, 1, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3206, 1, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (3207, 1, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (3208, 1, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3209, 1, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3210, 1, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3211, 1, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (3212, 1, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3213, 1, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3214, 1, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (3215, 1, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3216, 1, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3217, 1, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3218, 1, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3219, 2, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3220, 2, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3221, 2, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3222, 2, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3223, 2, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3224, 2, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3225, 2, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3226, 2, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3227, 2, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3228, 2, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3229, 2, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3230, 2, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3231, 2, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3232, 2, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3233, 2, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3234, 2, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3235, 2, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3236, 2, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3237, 2, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3238, 2, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3239, 2, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3240, 2, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3241, 2, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3242, 2, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3243, 2, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3244, 2, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3245, 2, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3246, 2, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3247, 2, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3248, 2, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3249, 2, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3250, 2, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3251, 2, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3252, 2, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3253, 2, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3254, 2, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3255, 2, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3256, 2, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3257, 2, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3258, 2, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3259, 2, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3260, 2, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3261, 2, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3262, 2, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3263, 2, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3264, 2, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3265, 2, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3266, 2, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3267, 2, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3268, 2, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3269, 2, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3270, 2, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3271, 2, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3272, 2, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3273, 2, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3274, 2, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3275, 2, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3276, 2, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3636, 6, 'project'); +INSERT INTO `pear_project_auth_node` VALUES (3637, 6, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (3638, 6, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3639, 6, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3640, 6, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (3641, 6, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3642, 6, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (3643, 6, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3644, 6, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3645, 6, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3646, 6, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3647, 6, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (3648, 6, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (3649, 6, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3650, 6, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3651, 6, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3652, 6, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (3653, 6, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (3654, 6, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (3655, 6, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3656, 6, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3657, 6, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (3658, 6, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3659, 6, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3660, 6, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (3661, 6, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3662, 6, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3663, 6, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3664, 6, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (3665, 6, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3666, 6, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3667, 6, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3668, 6, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3669, 6, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3670, 6, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3671, 6, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3672, 6, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3673, 6, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3674, 6, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3675, 6, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3676, 6, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (3677, 6, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (3678, 6, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (3679, 6, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (3680, 6, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (3681, 6, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (3682, 6, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3683, 6, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3684, 6, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (3685, 6, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (3686, 6, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3687, 6, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3688, 6, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3689, 6, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3690, 6, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3691, 6, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3692, 6, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3693, 6, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3694, 6, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3695, 6, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3696, 6, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3697, 6, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3698, 6, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3699, 6, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (3700, 6, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3701, 6, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3702, 6, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3703, 6, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3704, 6, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3705, 6, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3706, 6, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3707, 6, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3708, 6, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (3709, 6, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (3710, 6, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (3711, 6, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3712, 6, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3713, 6, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (3714, 6, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3715, 6, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3716, 6, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3717, 6, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (3718, 6, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3719, 6, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3720, 6, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3721, 6, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3722, 6, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3723, 6, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (3724, 6, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3725, 6, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3726, 6, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3727, 6, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3728, 6, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3729, 6, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3730, 6, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3731, 6, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3732, 6, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3733, 6, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3734, 6, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3735, 6, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3736, 6, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3737, 6, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3738, 6, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3739, 6, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3740, 6, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3741, 6, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (3742, 6, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3743, 6, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3744, 6, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3745, 6, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (3746, 6, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (3747, 6, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3748, 6, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3749, 6, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3750, 6, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (3751, 6, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3752, 6, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3753, 6, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (3754, 6, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3755, 6, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3756, 6, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3757, 6, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3758, 7, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3759, 7, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3760, 7, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3761, 7, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3762, 7, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3763, 7, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3764, 7, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3765, 7, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3766, 7, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3767, 7, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3768, 7, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3769, 7, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3770, 7, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3771, 7, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3772, 7, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3773, 7, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3774, 7, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3775, 7, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3776, 7, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3777, 7, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3778, 7, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3779, 7, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3780, 7, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3781, 7, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3782, 7, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3783, 7, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3784, 7, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3785, 7, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3786, 7, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3787, 7, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3788, 7, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3789, 7, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3790, 7, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3791, 7, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3792, 7, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3793, 7, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3794, 7, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3795, 7, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3796, 7, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3797, 7, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3798, 7, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3799, 7, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3800, 7, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3801, 7, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3802, 7, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3803, 7, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3804, 7, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3805, 7, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3806, 7, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3807, 7, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3808, 7, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3809, 7, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3810, 7, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3811, 7, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3812, 7, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3813, 7, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3814, 7, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3815, 7, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3816, 8, 'project'); +INSERT INTO `pear_project_auth_node` VALUES (3817, 8, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (3818, 8, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3819, 8, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3820, 8, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (3821, 8, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3822, 8, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (3823, 8, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3824, 8, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3825, 8, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3826, 8, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3827, 8, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (3828, 8, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (3829, 8, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3830, 8, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3831, 8, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3832, 8, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (3833, 8, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (3834, 8, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (3835, 8, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3836, 8, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3837, 8, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (3838, 8, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3839, 8, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3840, 8, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (3841, 8, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3842, 8, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3843, 8, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3844, 8, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (3845, 8, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3846, 8, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3847, 8, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3848, 8, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3849, 8, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3850, 8, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3851, 8, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3852, 8, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3853, 8, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3854, 8, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3855, 8, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3856, 8, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (3857, 8, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (3858, 8, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (3859, 8, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (3860, 8, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (3861, 8, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (3862, 8, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3863, 8, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3864, 8, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (3865, 8, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (3866, 8, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3867, 8, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3868, 8, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3869, 8, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3870, 8, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3871, 8, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3872, 8, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3873, 8, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3874, 8, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3875, 8, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3876, 8, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3877, 8, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3878, 8, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3879, 8, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (3880, 8, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3881, 8, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3882, 8, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3883, 8, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3884, 8, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3885, 8, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3886, 8, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3887, 8, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3888, 8, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (3889, 8, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (3890, 8, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (3891, 8, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3892, 8, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3893, 8, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (3894, 8, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3895, 8, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3896, 8, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3897, 8, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (3898, 8, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3899, 8, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3900, 8, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3901, 8, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3902, 8, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3903, 8, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (3904, 8, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3905, 8, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3906, 8, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3907, 8, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3908, 8, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3909, 8, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3910, 8, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3911, 8, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3912, 8, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3913, 8, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3914, 8, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3915, 8, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3916, 8, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3917, 8, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3918, 8, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3919, 8, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3920, 8, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3921, 8, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (3922, 8, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3923, 8, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3924, 8, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3925, 8, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (3926, 8, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (3927, 8, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3928, 8, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3929, 8, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3930, 8, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (3931, 8, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3932, 8, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3933, 8, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (3934, 8, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3935, 8, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3936, 8, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3937, 8, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3938, 9, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3939, 9, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3940, 9, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3941, 9, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3942, 9, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3943, 9, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3944, 9, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3945, 9, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3946, 9, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3947, 9, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3948, 9, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3949, 9, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3950, 9, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3951, 9, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3952, 9, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3953, 9, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3954, 9, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3955, 9, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3956, 9, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3957, 9, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3958, 9, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3959, 9, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3960, 9, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3961, 9, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3962, 9, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3963, 9, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3964, 9, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3965, 9, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3966, 9, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3967, 9, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3968, 9, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3969, 9, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3970, 9, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3971, 9, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3972, 9, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3973, 9, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3974, 9, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3975, 9, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3976, 9, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3977, 9, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3978, 9, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3979, 9, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3980, 9, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3981, 9, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3982, 9, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3983, 9, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3984, 9, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3985, 9, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3986, 9, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3987, 9, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3988, 9, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3989, 9, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3990, 9, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3991, 9, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3992, 9, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3993, 9, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3994, 9, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3995, 9, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3996, 4, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3997, 4, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3998, 4, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3999, 4, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (4000, 4, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4001, 4, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4002, 4, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (4003, 4, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4004, 4, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (4005, 4, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (4006, 4, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (4007, 4, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (4008, 4, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (4009, 4, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (4010, 4, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (4011, 4, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (4012, 4, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (4013, 4, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (4014, 4, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (4015, 4, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (4016, 4, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (4017, 4, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (4018, 4, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (4019, 4, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (4020, 4, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4021, 4, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (4022, 4, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (4023, 4, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (4024, 4, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (4025, 4, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4026, 4, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4027, 4, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4028, 4, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4029, 4, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (4030, 4, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (4031, 4, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (4032, 4, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (4033, 4, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4034, 4, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4035, 4, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (4036, 4, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4037, 4, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (4038, 4, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (4039, 4, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (4040, 4, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (4041, 4, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4042, 4, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (4043, 4, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (4044, 4, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (4045, 4, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4046, 4, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4047, 4, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (4048, 4, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (4049, 4, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4050, 4, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4051, 4, 'project/file/index'); +INSERT INTO `pear_project_auth_node` VALUES (4052, 4, 'project/file/read'); +INSERT INTO `pear_project_auth_node` VALUES (4053, 4, 'project/file/uploadfiles'); +INSERT INTO `pear_project_auth_node` VALUES (4054, 4, 'project/task/datetotalforproject'); +INSERT INTO `pear_project_auth_node` VALUES (4055, 4, 'project/project/getlogbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (4056, 4, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (4057, 3, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (4058, 3, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (4059, 3, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (4060, 3, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (4061, 3, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4062, 3, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (4063, 3, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (4064, 3, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (4065, 3, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (4066, 3, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (4067, 3, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (4068, 3, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (4069, 3, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4070, 3, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (4071, 3, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (4072, 3, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (4073, 3, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (4074, 3, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (4075, 3, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (4076, 3, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (4077, 3, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (4078, 3, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4079, 3, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4080, 3, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4081, 3, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (4082, 3, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4083, 3, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4084, 3, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (4085, 3, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (4086, 3, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4087, 3, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (4088, 3, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (4089, 3, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (4090, 3, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (4091, 3, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (4092, 3, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (4093, 3, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (4094, 3, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (4095, 3, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (4096, 3, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (4097, 3, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (4098, 3, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (4099, 3, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (4100, 3, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (4101, 3, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (4102, 3, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (4103, 3, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (4104, 3, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (4105, 3, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (4106, 3, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (4107, 3, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (4108, 3, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (4109, 3, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (4110, 3, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (4111, 3, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (4112, 3, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4113, 3, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (4114, 3, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (4115, 3, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (4116, 3, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (4117, 3, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4118, 3, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4119, 3, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4120, 3, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (4121, 3, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4122, 3, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (4123, 3, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (4124, 3, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4125, 3, 'project/project/getlogbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (4126, 3, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (4127, 3, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (4128, 3, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (4129, 3, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (4130, 3, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (4131, 3, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (4132, 3, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (4133, 3, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (4134, 3, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4135, 3, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (4136, 3, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4137, 3, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4138, 3, 'project/project_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (4139, 3, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4140, 3, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (4141, 3, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (4142, 3, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (4143, 3, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4144, 3, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4145, 3, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (4146, 3, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (4147, 3, 'project/task/datetotalforproject'); +INSERT INTO `pear_project_auth_node` VALUES (4148, 3, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4149, 3, 'project/task/tasksources'); +INSERT INTO `pear_project_auth_node` VALUES (4150, 3, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (4151, 3, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (4152, 3, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (4153, 3, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (4154, 3, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4155, 3, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (4156, 3, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4157, 3, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (4158, 3, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (4159, 3, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (4160, 3, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (4161, 3, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4162, 3, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4163, 3, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (4164, 3, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4165, 3, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4166, 3, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (4167, 3, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (4168, 3, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (4169, 3, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (4170, 3, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4171, 3, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (4172, 3, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4173, 3, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4174, 3, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4175, 3, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (4176, 3, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (4177, 3, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4178, 3, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4179, 3, 'project/source_link'); +INSERT INTO `pear_project_auth_node` VALUES (4180, 3, 'project/source_link/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4181, 3, 'project/task/tasklog'); +INSERT INTO `pear_project_auth_node` VALUES (4182, 3, 'project/task/recyclebatch'); +INSERT INTO `pear_project_auth_node` VALUES (4183, 3, 'project/file'); +INSERT INTO `pear_project_auth_node` VALUES (4184, 3, 'project/file/index'); +INSERT INTO `pear_project_auth_node` VALUES (4185, 3, 'project/file/read'); +INSERT INTO `pear_project_auth_node` VALUES (4186, 3, 'project/file/uploadfiles'); +INSERT INTO `pear_project_auth_node` VALUES (4187, 3, 'project/file/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4188, 3, 'project/file/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (4189, 3, 'project/file/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (4190, 3, 'project/file/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4191, 3, 'project'); + +-- ---------------------------- +-- Table structure for pear_project_collection +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_collection`; +CREATE TABLE `pear_project_collection` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '项目id', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加入时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目-收藏表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_collection +-- ---------------------------- +INSERT INTO `pear_project_collection` VALUES (29, 'n5opgqevrz1l03h48uwx67d2', '6v7be19pwman2fird04gqu53', '2019-01-02 17:40:18'); +INSERT INTO `pear_project_collection` VALUES (30, '8ulzfth64cd0k1x5peivowm2', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 09:16:15'); +INSERT INTO `pear_project_collection` VALUES (35, 'elqa703jyvfhpt1dsxkzi8on', '6v7be19pwman2fird04gqu53', '2019-01-04 21:44:19'); + +-- ---------------------------- +-- Table structure for pear_project_config +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_config`; +CREATE TABLE `pear_project_config` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `project_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '项目配置表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for pear_project_log +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_log`; +CREATE TABLE `pear_project_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '操作人id', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '操作内容', + `remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'create' COMMENT '操作类型', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '添加时间', + `source_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务id', + `action_type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '场景类型', + `to_member_code` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0', + `is_comment` tinyint(1) NULL DEFAULT 0 COMMENT '是否评论,0:否', + `project_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `icon` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4334 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目日志表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_log +-- ---------------------------- +INSERT INTO `pear_project_log` VALUES (3865, 'hb8l9ca23fv6konryzetwid5', '6v7be19pwman2fird04gqu53', '', '重做了任务', 'redo', '2018-12-29 10:15:50', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3866, '7x0cmb8jezoihy2nrspqdav6', '6v7be19pwman2fird04gqu53', '', '完成了任务', 'done', '2018-12-29 10:15:55', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3867, 'l5pem3zy2ts0oa87rf64cv9u', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了内容', 'name', '2018-12-29 10:16:10', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3868, 'va9og4r2e076puhw8d3k51xl', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了内容', 'name', '2018-12-29 10:16:10', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3869, 'u8yzk57ndfpj60rwes4b1la3', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了内容', 'name', '2018-12-29 10:16:56', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3870, '4ys53x2eoaimgpdrhuzk9ncw', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了备注', 'content', '2018-12-29 10:17:20', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (3871, '3jnmaiwh2k0gse5z6ycb9qul', '6v7be19pwman2fird04gqu53', '', '更新截止时间为DecDec月SunSun日 2222:1212', 'setEndTime', '2018-12-29 10:21:25', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3872, 'yxdhomi1jzu38plq42v0gar7', '6v7be19pwman2fird04gqu53', '', '更新截止时间为12月30日 22:04', 'setEndTime', '2018-12-29 10:23:47', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3873, '9vnih5ysb6aj2gd1rpu47xzk', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 22:04', 'setEndTime', '2018-12-29 10:24:26', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3874, 'b7qloh6psyzevmr84fcd3n9x', '6v7be19pwman2fird04gqu53', '', '清除了截止时间 ', 'clearEndTime', '2018-12-29 10:25:41', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3875, 'j3oiehapycrg16v8km9zdq2f', '6v7be19pwman2fird04gqu53', '', '移除了执行者 vilson.2', 'removeExecutor', '2018-12-29 10:49:20', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3876, 'azp3jxlsk04w9vfdqg1ho27y', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson', 'assign', '2018-12-29 10:51:06', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3877, 'crjuwktfns89d2l6ypog3qbm', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 10:52:00', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3878, '3pbxrfhc5nyil20gzjdweva6', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 10:52:06', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3879, 'bal3tv27mjqn1riusfywokcz', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson.2', 'removeMember', '2018-12-29 10:52:12', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3880, 't4o85dv2mi6qr9yn7e30asbh', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson.2', 'inviteMember', '2018-12-29 10:52:26', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (3881, '0gd32z4bwmexs7lthurfynqv', '6v7be19pwman2fird04gqu53', '', '移除了执行者 vilson', 'removeExecutor', '2018-12-29 10:52:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3882, '6bzg8p1u5f0j9dintrhoak37', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson', 'removeMember', '2018-12-29 10:52:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3883, '7z0pdbgmkfqnhyc9wsrau2vx', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson.2', 'removeMember', '2018-12-29 10:52:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3884, '0ca81iu4d5m9zj3o6ekvw7sq', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson', 'inviteMember', '2018-12-29 10:53:23', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (3885, '6w0ylbu1si5pg9mtjovqhrnd', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson.2', 'inviteMember', '2018-12-29 10:53:23', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (3886, 'c6s7evftijkbal5huo8mqrwd', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson', 'removeMember', '2018-12-29 10:53:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3887, '7c6qswjvx95rne8dy10h2bli', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson.2', 'removeMember', '2018-12-29 10:53:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3888, 'gs2ykol8h95af76mczx04jbd', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 10:57:21', 'hj5s73zk6amd9wfvbxoygpic', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3889, '54pznxmvueifhrok3lbj17a9', '6v7be19pwman2fird04gqu53', '', '完成了子任务 ', 'doneChild', '2018-12-29 10:57:21', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3890, '5uqkjha30czp4xltbv12dmwy', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 10:57:25', '4mtnhwbe0gjdkaur2ic7xsv6', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3891, 'dtbn6e3kiz42aly05qjuf9v8', '6v7be19pwman2fird04gqu53', '', '重做了子任务 ', 'redoChild', '2018-12-29 10:57:25', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3892, 'tpeohjivxzds8n7mwk4ba6yg', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 10:57:31', 'g83vs5t47dfnprchqzel1a29', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3893, 'n82qfxou094y5vg76rdcsbph', '6v7be19pwman2fird04gqu53', '', '完成了子任务 ', 'doneChild', '2018-12-29 10:57:31', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3894, 'waog819ziun43ql5ptf7hs2r', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 10:58:47', 'hj5s73zk6amd9wfvbxoygpic', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3895, 'imy4zkq7v8ehb02nl9uos16x', '6v7be19pwman2fird04gqu53', '', '重做了子任务 \'正在测试66\'', 'redoChild', '2018-12-29 10:58:47', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3896, 'r1hsabj6xm74p5qvf0ltcw92', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 10:59:38', '0zvn3ug6fiqhdpkljos79xaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3897, '0k6zht3acignxrs785b9pewl', '6v7be19pwman2fird04gqu53', '', '重做了子任务 “正在测试66”', 'redoChild', '2018-12-29 10:59:38', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3898, '967lzeit5d4an3yxbwhpomqg', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 11:00:21', '6kqh4b1mce05rzvuljsg3ow8', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3899, 'eizs4oy06gdkb9rvu1x2q7jc', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"正在测试66\"', 'doneChild', '2018-12-29 11:00:21', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3900, 'src7od6qp4age5umt3yb1kf2', '6v7be19pwman2fird04gqu53', '12', '创建了任务 ', 'create', '2018-12-29 11:02:39', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3901, 'uiyh86tmclzv75pwojefdrq1', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 11:02:39', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3902, 'qj59cy1iom4axe8znvhgfl2k', '6v7be19pwman2fird04gqu53', '88', '创建了任务 ', 'create', '2018-12-29 11:02:49', 'v2kr731dihezctslmx9agb5w', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3903, 'ngcptv1mkb08sde5uorfa4zw', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2018-12-29 11:02:49', 'v2kr731dihezctslmx9agb5w', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3904, 'rjyt3nkd6bs4e9owf2qv0xpu', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 11:03:30', 'v2kr731dihezctslmx9agb5w', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3905, '6du4nlszheact9v7rpqjkbgw', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 11:16:37', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3906, 'e6n89tqal4pkf3c02xo1i7vh', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了备注 ', 'content', '2018-12-29 12:09:11', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (3907, 'dgt56ixya7bm9ljp8cr2wsh4', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 12:10:01', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3908, '9uxe30yni8lhpbdcm2t74fjq', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2018-12-29 12:10:03', 'edbn6rz89fmh7a3ilgvukw14', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3909, 'xdnbuiyl36etvzwhp9f5o07g', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月29日 18:00', 'setEndTime', '2018-12-29 12:10:06', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3910, 'zjirf8e054oscx3mlu2b9gp1', '6v7be19pwman2fird04gqu53', '22', '更新了备注 ', 'content', '2018-12-29 12:10:10', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (3911, 'bjsgzv69hap8xoc4i5umq1re', '6v7be19pwman2fird04gqu53', '22', '更新了内容 ', 'name', '2018-12-29 12:10:11', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3912, 'zk6vu1pin4jy2f9w0gc5b73m', '6v7be19pwman2fird04gqu53', '88', '创建了任务 ', 'create', '2018-12-29 12:10:16', '1vqt5zg4shbkum2dc8jxow7f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3913, 'qw03pol295t1diz846erycsj', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:10:16', '1vqt5zg4shbkum2dc8jxow7f', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3914, 'bml3dzxq6a07tgv9k1u8nwe2', '6v7be19pwman2fird04gqu53', '99', '创建了任务 ', 'create', '2018-12-29 12:11:40', '2hg0yfa9jpxz6q58ckrdol1u', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3915, 'oynufrwkag4lvqc7pbszm62d', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"22\"', 'createChild', '2018-12-29 12:11:40', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3916, 't69wv1zanypbxj2srg34ifkm', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:11:40', '2hg0yfa9jpxz6q58ckrdol1u', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3917, '7k6rt1vmhcdb89jxawsiz23n', '6v7be19pwman2fird04gqu53', '77', '创建了任务 ', 'create', '2018-12-29 12:11:57', 'bi5ajpmxfsk9rwdg4l32yv1n', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3918, 'bs85197y2hl4gzaowp6ujxfc', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"22\"', 'createChild', '2018-12-29 12:11:57', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3919, 'klijc6v9hy8dx457mfp3otra', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:11:57', 'bi5ajpmxfsk9rwdg4l32yv1n', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3920, 'h72aki9vsqzf0lrwb18n6yd3', '6v7be19pwman2fird04gqu53', '12', '创建了任务 ', 'create', '2018-12-29 12:12:32', 'dmbtgy3phi2sz7j89q10xcoa', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3921, '97t5lacuwmre4v8jsh0g3q2p', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"22\"', 'createChild', '2018-12-29 12:12:32', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3922, '9dok2m0rjq81zsbptc45x7yf', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:12:32', 'dmbtgy3phi2sz7j89q10xcoa', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3923, 'jhmi9ud8nxv7t26bzf5wspc0', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:12:41', 'ywiahqcb50rkem376js9nflv', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3924, 'vswa2btoujq5ycmfz4193npk', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:12:41', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3925, 'm8195lgdibutzjrqo23aeh06', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:12:41', 'ywiahqcb50rkem376js9nflv', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3926, 'ylz9erqbjn3d0hxv8go6fitm', '6v7be19pwman2fird04gqu53', '2', '创建了任务 ', 'create', '2018-12-29 12:12:42', 'xebf08p2uc9sj6mkr5714lat', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3927, 'oy0f23w568vg9jlqapebmdns', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:12:42', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3928, 'xsfe94pvt87mbyi36oqrk10c', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:12:42', 'xebf08p2uc9sj6mkr5714lat', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3929, 'hxajk4rplqoe9gn1tu8672fb', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-29 12:12:44', 'p5axvq94lr7enmhb83o2zfks', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3930, 'zmb27c59vxne6jar3if04hlk', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:12:44', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3931, 'v0t2yzoch1a6urex3lb8npfw', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:12:44', 'p5axvq94lr7enmhb83o2zfks', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3932, '2kvg64szutj5amh89d1q0i7w', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-29 12:14:21', 'lert5uyi790q2jfpbmzgak1o', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3933, 'b8l3jgk10a6h5mq2pyz9eoiv', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:14:21', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3934, 'pe7mrlxw2iqoyg6ka3bcfszh', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:14:21', 'lert5uyi790q2jfpbmzgak1o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3935, 'ouci27hjl9f56mxekwypdv3g', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:14:30', '25rzhoykix6pajsw9cgm1408', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3936, '2pma6dyvwj9ktrbe0u58igzl', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:14:30', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3937, 'bj6hlw48vcmori9es0pzfdun', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:14:30', '25rzhoykix6pajsw9cgm1408', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3938, '06sw7kf1ricaqg2yoxnd8buj', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:15:33', 'cmrdn8soz4g26fy0qa3ib7te', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3939, 'pu8jn0labrgs3w7qxoh2i1ct', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"333\"', 'createChild', '2018-12-29 12:15:33', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3940, 'jsyp6c5a1elmidvnk89g472r', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:15:33', 'cmrdn8soz4g26fy0qa3ib7te', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3942, 'wkdzbvgh6stn7r1i4yxpoj9q', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-29 12:16:19', 'qfakryig3ztpv5uw6e2obx08', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3943, 'e3z5lfu497no06hqrwdmcxgb', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"3\"', 'createChild', '2018-12-29 12:16:19', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3944, 'g8d693u0qopr5msa2itvhejc', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:16:19', 'qfakryig3ztpv5uw6e2obx08', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3945, 'thw032pbsjf8q61yoadk94eg', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-29 12:22:17', 'e16tvkg4uoixnz9hjaf5l3wr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3946, 'km0t9szd1gc37alpujqhf8ry', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"4\"', 'createChild', '2018-12-29 12:22:17', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3947, 'witaxj3zvcf58rbq0y6s97nm', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:17', 'e16tvkg4uoixnz9hjaf5l3wr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3948, 'l1u2ncj9deq6i5bp8tysrg3z', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:22:18', 'f35oyrln286s7p4u9vmeghdw', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3949, 'efgjtyv1d3uckbos62xa078h', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"5\"', 'createChild', '2018-12-29 12:22:18', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3950, 'w1n6gshr5ftk3b0pjvqxyld2', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:18', 'f35oyrln286s7p4u9vmeghdw', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3951, '6wfip04jx5h8nvdzes29gb1y', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-29 12:22:19', 'yqwe2aiupvg5zj1so4krm86l', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3952, 'rt7jwfvlsi0hbazod9mc2x6n', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"6\"', 'createChild', '2018-12-29 12:22:19', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3953, '3y5rmwga8dfi9npulbv2c01q', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:19', 'yqwe2aiupvg5zj1so4krm86l', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3954, 'jnlt9q7udp86rme0h4vogk3s', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:22:22', 'yhs4xb7g3vu9pnwfq5l1zioa', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3955, 'd5ji698spacgwzq7n2tky3ru', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:22:22', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3956, '4b90ze26hmprkw5adxjy8ivt', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:22', 'yhs4xb7g3vu9pnwfq5l1zioa', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3957, '5f0xugvdj3wp46hyte7lnamk', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:22:52', 'jo3vbswk8ze57filmcptun2g', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3958, '7bmtyslzh0w8aeu213onq69p', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"5\"', 'createChild', '2018-12-29 12:22:52', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3959, 'rwy5p9fnkbc0ds4ltoeiz7am', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:52', 'jo3vbswk8ze57filmcptun2g', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3960, 'paxcuosjmf7g831zl0ndvhbe', '6v7be19pwman2fird04gqu53', '8', '创建了任务 ', 'create', '2018-12-29 12:22:53', '35i8ptd1fs9hrbk0glv47ycj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3961, 'mh0fe4tr9iup72jn85z6xay3', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"8\"', 'createChild', '2018-12-29 12:22:53', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3962, '6jurq17m9fl43ekcn5x8y2oa', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:53', '35i8ptd1fs9hrbk0glv47ycj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3963, '7o12ivmp9ygu5kjclzn6rwd3', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:22:54', 'pn3dmjavhrc1bewgi4yz25x9', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3964, 'k7dht9cq4e01vns2ojxgiyb8', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:22:54', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3965, 'cl5s4ro3m1x9hejf8dbyzatk', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:54', 'pn3dmjavhrc1bewgi4yz25x9', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3966, '54taxf2qnbymd9wiczo0rpve', '6v7be19pwman2fird04gqu53', '9', '创建了任务 ', 'create', '2018-12-29 12:22:55', 'p5r8a4gwfxmn9sqit2jvkl71', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3967, 'y10gb254fwrimqnu9jszkc73', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"9\"', 'createChild', '2018-12-29 12:22:55', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3968, 'j8m1lxeschgiw6ot95n4r2av', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:55', 'p5r8a4gwfxmn9sqit2jvkl71', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3969, 'xvlo3je8y61trsi0wdm7ac45', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:23:00', 'aenxk82rgwm5qcsuo0b7hi4f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3970, '3ynx5bf61qmev4rjl2w8gksc', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"1\"', 'createChild', '2018-12-29 12:23:00', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3971, 'hkpx0i56wftbj7o2c84agl1m', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:23:00', 'aenxk82rgwm5qcsuo0b7hi4f', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3972, 'cuhw96e2mk5ft0rpalgb8doq', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:24:25', 'vf8xtpclba3od21kmih5us9q', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3973, 'fm9oyz65ajn0xqb81wc3ievr', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:24:25', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3974, '952evikzqod30sh6a41mbgru', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:24:25', 'vf8xtpclba3od21kmih5us9q', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3975, 'l4merydg0x3jkbiuowqc28fs', '6v7be19pwman2fird04gqu53', '9', '创建了任务 ', 'create', '2018-12-29 12:24:26', '2ohuv1jm3abrs8ldni5p9z06', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3976, 'u1d53a8ecxvnl9zpth0qgw4j', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"9\"', 'createChild', '2018-12-29 12:24:26', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3977, 'wtc2u0jsx9h4o8vz1p376mrl', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:24:26', '2ohuv1jm3abrs8ldni5p9z06', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3978, '0adg97cq2ipl5bmvj13rkt4w', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:29:39', 'wamzl34n6jfrhiyoe09v2ktb', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3979, 'cjf17vsmiz84raxk52eunwdh', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"1\"', 'createChild', '2018-12-29 12:29:39', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3980, 'buw4ozsyxakdit6e5vf3l7q9', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:39', 'wamzl34n6jfrhiyoe09v2ktb', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3981, '4osxw90np216glrhumzk7fcj', '6v7be19pwman2fird04gqu53', '2', '创建了任务 ', 'create', '2018-12-29 12:29:40', 'rgshi61c4xol5ue9f2n8m3bd', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3982, 'e0u7act8qihwdm3nor9bxv5z', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"2\"', 'createChild', '2018-12-29 12:29:40', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3983, 'pi7y3njsxumgchbf6r2vozqd', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:40', 'rgshi61c4xol5ue9f2n8m3bd', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3984, 'cbx4gq9voi51dz06lfykhp2w', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-29 12:29:41', 'kny3xf7o41rli9s0pwjmhdqc', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3985, 'mt8qukh2w6xdypoger7zis54', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"3\"', 'createChild', '2018-12-29 12:29:41', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3986, 'wy2ch8xs7ftmk93rlnivbeu4', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:41', 'kny3xf7o41rli9s0pwjmhdqc', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3987, 'dmike23ryw5qhf0anxpt46bj', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-29 12:29:42', 'y2zos3hg9058alwet46xmukp', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3988, '9juosez4wvklpdchq37826nt', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"4\"', 'createChild', '2018-12-29 12:29:42', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3989, 'fpjz6s58owxd20b3irhkynm1', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:42', 'y2zos3hg9058alwet46xmukp', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3990, 'xmudezpvr4613atijfqlwhyn', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:29:43', 'wqu7verc5i4p19h0y32x6tmo', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3991, 'upa9vqkys30fmejcz4nh1rwd', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"5\"', 'createChild', '2018-12-29 12:29:43', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3992, 'uwvdnqm1y893irt5jc46sze0', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:43', 'wqu7verc5i4p19h0y32x6tmo', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3993, 'egms06q7hnw4ulf3p1jvotkb', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-29 12:29:44', '0h8mt7sw1ki5fuxv2y6d3jag', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3994, 'ax842wv1uenslbizyth7om0p', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"6\"', 'createChild', '2018-12-29 12:29:44', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3995, '85hnga9li4mfqv1weo6tyzds', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:44', '0h8mt7sw1ki5fuxv2y6d3jag', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3996, 's0oqyw9z1ukhmd7ce2xpf5v4', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:29:46', '1hcrmvgfjtu5qnadl869bkxo', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3997, 'c4hdo7sxeqam9gt5runbl0py', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:29:46', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3998, 'udfj9135zixo7g2rway6mknc', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:46', '1hcrmvgfjtu5qnadl869bkxo', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3999, 'k6h170ma4zteuyb9dp8nicsf', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-29 12:29:54', 'puj84l5av3f0e7k9oyw2zqcr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4000, 'gk7bdt2pmilwc8hunx19y6zq', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"6\"', 'createChild', '2018-12-29 12:29:54', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4001, 'adhr0wxonpt27zk9ecq4m6u1', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:54', 'puj84l5av3f0e7k9oyw2zqcr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4002, 'se7w26u1xzk8cbm4oqfn3rpy', '6v7be19pwman2fird04gqu53', '8', '创建了任务 ', 'create', '2018-12-29 12:29:56', 'jftz4y5is1hwrlmac07nd8q6', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4003, '0m3hn9v7x2q4ig6etzwfo8kd', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"8\"', 'createChild', '2018-12-29 12:29:56', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4004, 'm7azwxsht603859gykcdpo2v', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:56', 'jftz4y5is1hwrlmac07nd8q6', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4005, 'jc7a30ur8bzgsvw29lxyinok', '6v7be19pwman2fird04gqu53', '415', NULL, 'comment', '2018-12-29 12:40:02', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4006, 'za961k07mbpd34vuoxfn8wc5', '6v7be19pwman2fird04gqu53', '8989', NULL, 'comment', '2018-12-29 12:40:21', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4007, 'j0k1h37iuqnocylw6fe94za2', '6v7be19pwman2fird04gqu53', '88ss', '更新了内容 ', 'name', '2018-12-29 12:47:25', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4008, 'v1e4dhlq7giuwk2bmz35ncf8', '6v7be19pwman2fird04gqu53', '88ss', '更新了备注 ', 'content', '2018-12-29 12:47:35', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4009, 'sdjkuz4bniprcha3qy6ow51m', '6v7be19pwman2fird04gqu53', '88ss', '更新了备注 ', 'content', '2018-12-29 12:47:41', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4010, 'rdnplfaxjvsuy8q39504eb7c', '6v7be19pwman2fird04gqu53', '88ss', '更新了备注 ', 'content', '2018-12-29 12:47:46', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4011, 'f4vq6utlabw7my1gnzx8390k', '6v7be19pwman2fird04gqu53', '99', NULL, 'comment', '2018-12-29 12:52:47', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4012, '5ope0jf6ytgqvl3m8xdh7bkz', '6v7be19pwman2fird04gqu53', 'dff', NULL, 'comment', '2018-12-29 12:58:22', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4013, 'd3b0xn69ljvpikcf2az4eg81', '6v7be19pwman2fird04gqu53', '8989', NULL, 'comment', '2018-12-29 12:58:24', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4014, 'aum2zlh4y38cj9t1nriebp07', '6v7be19pwman2fird04gqu53', '13\n55\n', NULL, 'comment', '2018-12-29 12:58:28', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4015, 'elwir0n5yzh4tbx1om87s3gk', '6v7be19pwman2fird04gqu53', '89\n\n\n66\n\n66\n', NULL, 'comment', '2018-12-29 12:59:23', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4016, 'tkxr8zwagy106f3ipmed7boj', '6v7be19pwman2fird04gqu53', 'sss
ee', NULL, 'comment', '2018-12-29 13:00:39', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4017, '5zdypx9jarfk836onmq2ibge', '6v7be19pwman2fird04gqu53', '9', NULL, 'comment', '2018-12-29 13:01:48', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4018, 'wh0v2fqp9nkix1sg56z8mljo', '6v7be19pwman2fird04gqu53', '测试', NULL, 'comment', '2018-12-29 13:01:52', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4019, '3xwke78sl49bragtovjhfp20', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 13:03:57', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4020, 'sdhi9e6kugrj2bnc83qt7y4w', '6v7be19pwman2fird04gqu53', '10', '创建了任务 ', 'create', '2018-12-29 13:05:03', '4fua38vpqgk706csx2lb9etj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4021, '6a14fuo902x73zqsrkvnywhg', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"10\"', 'createChild', '2018-12-29 13:05:03', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4022, 'fn3bho7stwlm92r6g4j1a5pk', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 13:05:03', '4fua38vpqgk706csx2lb9etj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4023, 'o7v31qb6ysi4fugat25chxmd', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:09:27', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4024, 'eycx8z7s3juo4git9pradqvh', '6v7be19pwman2fird04gqu53', '

这里是备注内容

', '更新了备注 ', 'content', '2018-12-29 13:10:28', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4025, 'q9hsz2valnwy0rfg8umpe3b7', '6v7be19pwman2fird04gqu53', '

这里是备注内容

', '更新了备注 ', 'content', '2018-12-29 13:10:58', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4026, 'zgonswqe0xvky469d2tfm3hj', '6v7be19pwman2fird04gqu53', 'dd', NULL, 'comment', '2018-12-29 13:16:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4027, 'pjntgqukcr041hazbsv29imd', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:40', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4028, 'cqigul6pm3y4vrnabf2xowt9', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:42', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4029, 'jv7s1q0mw2hxdgfpk8r5zbuc', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:50', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4030, '5fgsnwq7xb2luvm6edz43yih', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:58', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4031, '7f3485jcnsz69a0dwtgrbvlu', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 13:19:29', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4032, 'lagx0bjhvmdt61ufrqi37zw8', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 非常紧急', 'pri', '2018-12-29 13:21:38', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4033, 'qfyrd27bjm93otwkne58p4ia', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 普通', 'pri', '2018-12-29 13:21:42', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4034, '576y0p2s8zkvqejcfga341ud', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 非常紧急', 'pri', '2018-12-29 13:21:46', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4035, 'lmn8pc5r0j39zvq26wsatoxf', '6v7be19pwman2fird04gqu53', '

这里是备注内容


', '更新了备注 ', 'content', '2018-12-29 13:25:21', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4036, 'qibmkuh0pfnatdcs4j2xyw1o', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson.2', 'inviteMember', '2018-12-29 13:25:31', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4037, 'wc7u9qmy628okg0zpt1iejb3', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2018-12-29 13:25:36', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4038, 'l0z6a8u1rcjkxnte3pwobfv5', '6v7be19pwman2fird04gqu53', 'Tree 的 @on-select-change 和 @on-check-change 事件返回参数新增当前项', '更新了内容 ', 'name', '2018-12-29 13:27:16', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4039, 'x8bgefzjv0p4iqhtlmwad7s3', '6v7be19pwman2fird04gqu53', '修复 Table 动态设置表头分组报错的问题', '更新了内容 ', 'name', '2018-12-29 13:27:35', '4mtnhwbe0gjdkaur2ic7xsv6', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4040, 'hlm2ei7403a9g1byzdvrkowc', '6v7be19pwman2fird04gqu53', '新增阿拉伯语', '更新了内容 ', 'name', '2018-12-29 13:27:51', 'hj5s73zk6amd9wfvbxoygpic', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4041, 'ctm4v1hlno20ksafdb8qjew9', '6v7be19pwman2fird04gqu53', 'Table 支持 slot-scope 用法', '更新了内容 ', 'name', '2018-12-29 13:28:02', 'l027b1dyrv93zu4ewmtoa6q5', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4042, 'e95h10orj3pu78lvbtdmczsk', '6v7be19pwman2fird04gqu53', 'Table 新增取消全选事件 @on-select-all-cancel。', '更新了内容 ', 'name', '2018-12-29 13:28:24', 'rqb7vi254tna3uzhdgo0f6ey', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4043, '5v8hz7qt2kgdjwaf46ui3nrl', '6v7be19pwman2fird04gqu53', 'Table 新增取消全选事件 @on-select-all-cancel', '更新了内容 ', 'name', '2018-12-29 13:28:26', 'rqb7vi254tna3uzhdgo0f6ey', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4044, 'pm524anfjqdvl1ukwr3gzhyx', '6v7be19pwman2fird04gqu53', '123', '创建了任务 ', 'create', '2018-12-29 16:08:33', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4045, 'qlyenmbjf264ochakvu39r0w', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 16:08:33', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4046, '4eagjuoc15v3f6z0brkdsxwm', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 21:44:15', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4047, 'b7z84xwlmr3cyjt15p9dqhoi', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 21:45:14', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4048, '9krvs6f5j03hyxoduae4n7gm', '6v7be19pwman2fird04gqu53', '

这里是备注内容


', '更新了备注 ', 'content', '2018-12-29 21:45:42', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4049, 'scxy36o5gpkltiujn1rm0v79', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson', 'inviteMember', '2018-12-29 21:47:17', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4050, '24x68z9pivtjlqabsdcg3ywk', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 21:47:20', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4051, '21ycsgt58k9urfql4nep7bda', '6v7be19pwman2fird04gqu53', 'Hi~', NULL, 'comment', '2018-12-29 21:47:28', 'aut9wrz1pn0elf5s47ivx26o', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4052, '4m9cj3eg6srqou2i5abxlwzh', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 09:04:09', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4053, 'f5x7v3sqhi1m4joy6cdbpu9t', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 10:22:49', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4054, '84bnae6csizdo3vph9kjw1q0', '6v7be19pwman2fird04gqu53', '123', '创建了任务 ', 'create', '2018-12-30 10:24:40', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4055, '1hyxiz4kt7j0pu9fs8enl5g6', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"123\"', 'createChild', '2018-12-30 10:24:40', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4056, 'djzr3kv7wicolasp2hmx96ub', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 10:24:40', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4057, 'vf8t0o5sp36hgazmijn1uewc', '6v7be19pwman2fird04gqu53', '222', '创建了任务 ', 'create', '2018-12-30 10:24:42', 'wrjgk84t2beam0yvxs61qinu', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4058, 'zaf14de3jrpobvins2kxm86u', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"222\"', 'createChild', '2018-12-30 10:24:42', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4059, 'agweirnvb41plzsfkx8jyt9c', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 10:24:42', 'wrjgk84t2beam0yvxs61qinu', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4060, 'usiaxdk1538bcty0mlgjrqoh', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-30 10:35:33', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4061, '5nyzc7djiefg80xtkqa32mpr', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"123\"', 'doneChild', '2018-12-30 10:35:33', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4062, '0zj738s1fit5blokvwgrmpy2', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-30 10:35:35', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4063, 'heyz2qimsw6un7txjc8pklvg', '6v7be19pwman2fird04gqu53', '', '重做了子任务 \"123\"', 'redoChild', '2018-12-30 10:35:35', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4064, 'fgwdb84p9oazs6xk3cmynlj7', '6v7be19pwman2fird04gqu53', '789', NULL, 'comment', '2018-12-30 10:38:05', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4065, 'xioz6hrkbtelsmfw4cgyv983', '6v7be19pwman2fird04gqu53', '', '恢复了任务 ', 'recovery', '2018-12-30 11:22:14', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4066, 'l7ncz0t8fxj4sam5u6whq39p', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 11:23:39', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4067, '32hzsjagcwdr0817f9o6i5vu', '6v7be19pwman2fird04gqu53', '', '恢复了任务 ', 'recovery', '2018-12-30 11:23:43', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4070, 'a58ekv7lmy4dx0tqjzs2iwr9', '6v7be19pwman2fird04gqu53', '阅读「分享」中的使用案例,为新产品发布计划建立一个公示板吧!', '更新了内容 ', 'name', '2018-12-30 13:31:43', '0tjma1un2gz8rf4ywo7c6de9', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4071, '4isl2cgedqz7utjo6kbv81ar', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 15:30:09', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4072, 'phm3ed5l1vogjycaqntxwk8b', '6v7be19pwman2fird04gqu53', '123', '创建了任务 ', 'create', '2018-12-30 15:38:02', 'n9pe164krv8zghofilw750jq', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4073, 'ca8ixjvhk3p40mfqo7lue6gy', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 15:38:03', 'n9pe164krv8zghofilw750jq', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4074, 'yz4urfgdkslc9jahq73vwt01', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 15:38:35', 'n9pe164krv8zghofilw750jq', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4075, 'z26cd0s5ntgj7prh1x9vk3uq', '6v7be19pwman2fird04gqu53', '444', '创建了任务 ', 'create', '2018-12-30 15:39:15', 'oq7d3wbklenf12pgvuxhimr8', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4076, 'prgjo7vtns9k6qha43b0uled', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 15:39:15', 'oq7d3wbklenf12pgvuxhimr8', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4077, 'yp26orn0w4am59xl3utiqsdg', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 15:39:19', 'oq7d3wbklenf12pgvuxhimr8', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4081, '63a1jteb4qflz9pnig8c2k5u', '6v7be19pwman2fird04gqu53', '', '清除了截止时间 ', 'clearEndTime', '2018-12-30 22:15:33', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4082, 'urzst152ka3ep4w7ndm06qyg', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月28日 18:00', 'setEndTime', '2018-12-30 22:15:36', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4083, 'xbf1rpj8nmv7l9u4d3w0qke5', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 08:50:07', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4084, 'mroi3xe1wchq9t2p0bvg7uf5', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 08:50:10', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4085, 'iok3hc24sn6eftjr0lx9b7dv', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:18:10', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4086, 'ukx5mvn7lihws3gobc2ty1pf', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月01日 18:00', 'setEndTime', '2018-12-31 09:20:39', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4087, '21y6n8o9f5p3xa7ihkglqrv0', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:27:09', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4088, 'v4g7qblyhwuat5p2c3znor1s', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 18:00', 'setEndTime', '2018-12-31 09:28:02', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4089, 'f86ehji9spwlaubgv4m2qnz5', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月29日 18:00', 'setEndTime', '2018-12-31 09:28:18', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4090, 'y3x41uv5tacmnpq7gofji28r', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月23日 18:00', 'setEndTime', '2018-12-31 09:28:30', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4091, 'em5bhyangxtiu7voczpwd3js', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:31:09', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4092, 'nbteasyp9hfg3z06wvkj71qi', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 18:00', 'setEndTime', '2018-12-31 09:32:06', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4093, 'ba26fwrk5q8x0tsvn4hmoedi', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月23日 18:00', 'setEndTime', '2018-12-31 09:38:24', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4094, 'ionamswe2gj7hzdqx9br01f8', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月28日 18:00', 'setEndTime', '2018-12-31 09:38:27', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4095, 'l6fhtq2gneva8yj571cd43zu', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月03日 18:00', 'setEndTime', '2018-12-31 09:38:31', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4096, 'ifroz4bhkla789quv6x3dcsm', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:45:14', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4097, 'ob0m4yrs6uglxdkatfqj52zw', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 18:00', 'setEndTime', '2018-12-31 09:45:22', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4098, 'm6faqh85rlxvedy3cpzujnbw', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:46:20', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4099, 'slroc7k1x3twdva50m28hf6p', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月01日 18:00', 'setEndTime', '2018-12-31 09:46:26', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4100, 'ncyk4b5ozwvuhrxd1sgp386f', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月08日 18:00', 'setEndTime', '2018-12-31 09:49:53', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4101, 'c5fynatdm0wgvqk1rhijsopz', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson', 'inviteMember', '2018-12-31 10:49:34', 'mv4usefb06dxv8ez2spkl223', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4102, '51hc28jqn64zyspxlwmvrgak', '6v7be19pwman2fird04gqu53', '', '添加了参与者 Chihiro', 'inviteMember', '2018-12-31 10:49:34', 'mv4usefb06dxv8ez2spkl223', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4103, '96rdvspjbz1qfnxcogeam7h8', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 14:07:44', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4104, '05k4jw2tv9zu67yhbd1oigsr', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 14:08:02', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4105, '186hgmstdjxa2b50cqovpfez', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 14:08:10', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4106, 'dkx85glpe20r7uw9osa13z4j', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 14:19:55', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4107, 'bw3iz9a6fc2ylq5sngxp8md7', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-31 14:26:54', 'mv4usefb06dxv8ez2spkl223', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4108, 'uy8b34ph5qlas0t6cej7wfir', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2018-12-31 14:55:20', 'mv4usefb06dxv8ez2spkl223', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4109, 'tawsz234c90xm71ko5yg6uev', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:00:09', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4110, 'kysizpn5rf2t90o3jvgw7ubl', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2018-12-31 15:04:04', 'mv4usefb06dxv8ez2spkl223', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4111, 'wgzq1pyexs8lk9jinh5btvm2', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-31 15:04:43', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4112, '83rkucdwm5xos7aqlztjn4ih', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:43', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4113, 'khgazoxwlesn325uftdyb948', '6v7be19pwman2fird04gqu53', '2', '创建了任务 ', 'create', '2018-12-31 15:04:44', 'rzpu5cxl63fvb2y8gwdnsjqk', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4114, 'm0iwj9q5duxbsnarp86tk31h', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:44', 'rzpu5cxl63fvb2y8gwdnsjqk', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4115, 'i603csgvdzwh7ouajbfp2ren', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-31 15:04:45', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4116, 'hpycx4681ijk3bn59gzsml2a', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:45', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4117, 'ncp9o3q2y4mzuvx7e5rb0jga', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-31 15:04:46', 'xejt6431q8ly97bkid5z2pun', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4118, 'kyrlbu51ofmtsv9qax82p0ni', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:46', 'xejt6431q8ly97bkid5z2pun', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4119, 'wdm01ato3hpjfubvc9n4z7ge', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-31 15:04:47', 'zkqb6if5ogdts27lx13r4yju', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4120, 'rklwiogfxd48qytpvne5zhmu', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:47', 'zkqb6if5ogdts27lx13r4yju', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4121, 'w581csf97ojqytu0hderx4bz', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-31 15:04:49', '9wsohy8jgapl6x2iutbm7k34', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4122, '93047cqlbavx8wr2puie61zg', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:49', '9wsohy8jgapl6x2iutbm7k34', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4123, 'zmf9i5tv6ryuxjol71e3hw40', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 15:05:09', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4124, 'xzduq1jiesmht89vkfo0p645', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 紧急', 'pri', '2018-12-31 15:05:22', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4125, 'dgt7mbakn1zx5huv93le4rj0', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2018-12-31 15:20:11', '9wsohy8jgapl6x2iutbm7k34', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4126, 'dq4j60pcg9st1maxwv7hozki', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:22:38', 'xkqg60sld15fcphwt4ya3rb8', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4127, 'brc67fhgxdsmv9kojtpan4ez', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 15:22:40', 'xkqg60sld15fcphwt4ya3rb8', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4128, 'fvxzwo24tur1np3s07qdbya9', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:23:01', 'xkqg60sld15fcphwt4ya3rb8', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4129, 'in2b7tx1pmfwjkhl38v54zoc', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:23:03', 'xejt6431q8ly97bkid5z2pun', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4130, 'lbt86sej72gwca0uno4kdh51', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-31 15:23:08', 'xejt6431q8ly97bkid5z2pun', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4131, 'j7xqfgbtns0huywr9po4z1va', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 17:55:13', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4132, 'pykstuo3210bn5iq76jg8wfz', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 17:55:15', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4133, 'me1ofzi6acr72hkb4pwtyvjg', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2019-01-01 12:41:11', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4134, 'uim9z48hg0p5jro23sfkxe67', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-01 12:41:13', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4135, 'gyrtq8b26ocxu3kdn4ef07z5', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2019-01-01 12:41:13', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4136, 's2unk6cav84ozqg7dtb10mrf', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-01 21:20:22', 'mv4usefb06dxv8ez2spkl223', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4137, 'tqbgjrp6vd8iks0hfnm91wc7', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 10:26:45', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4138, 't7eor5w2x6sf4u3n8hajlgic', 'kqdcn2w40p58r31zyo6efjib', '1', '创建了任务 ', 'create', '2019-01-03 10:46:04', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4139, 'evbyapm7qck64i89j3rgohw5', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-03 10:46:04', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4140, 'v20o3mcpb65awy4zf8le7h1i', 'kqdcn2w40p58r31zyo6efjib', '2', '创建了任务 ', 'create', '2019-01-03 10:46:13', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4141, '5ori2dcnubymxgz7j3968s1e', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-03 10:46:14', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4142, '7h6fiyn52vmt1kcqs9pgolab', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 10:49:35', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4143, 'qakjgy7sxw25rvnize8pfdtm', 'kqdcn2w40p58r31zyo6efjib', '', '重做了任务 ', 'redo', '2019-01-03 10:49:37', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4144, 'qehcwy2mupsi8jrb4105lv7t', 'kqdcn2w40p58r31zyo6efjib', '', '添加了参与者 vilson', 'inviteMember', '2019-01-03 10:51:44', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4145, 'tmr1xvy4hz97fj8nsegoib0w', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-03 10:51:51', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4146, '8fubhgml3dx16krpto2e5sv9', 'kqdcn2w40p58r31zyo6efjib', '', '移除了执行者 ', 'removeExecutor', '2019-01-03 10:51:53', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4147, 'qgsbed0ozp27nwirlfk8yatj', 'kqdcn2w40p58r31zyo6efjib', '

66

', '更新了备注 ', 'content', '2019-01-03 10:52:06', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4148, 'zh3k4aotncqu2bw16yr75mfg', 'kqdcn2w40p58r31zyo6efjib', '', '移除了参与者 vilson.2', 'removeMember', '2019-01-03 10:58:16', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4149, 'zfewmyds7q14li2vj56rngpc', 'kqdcn2w40p58r31zyo6efjib', '99', '创建了任务 ', 'create', '2019-01-03 11:00:15', 'm6cloqrbh7tf0wg1jsvp9nay', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4150, 'h7pilc01go6a2qj5e3stxwzb', 'kqdcn2w40p58r31zyo6efjib', '', '添加了子任务 \"99\"', 'createChild', '2019-01-03 11:00:15', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4151, '9kpj26uxbdetlgiqcsm5a8zh', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 Chihiro', 'assign', '2019-01-03 11:00:15', 'm6cloqrbh7tf0wg1jsvp9nay', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4152, 'd1onu8sbwl62hy9tcpjkfeaq', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 11:01:30', 'm6cloqrbh7tf0wg1jsvp9nay', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4153, '148jmclitfbvkx95ry2ed3nz', 'kqdcn2w40p58r31zyo6efjib', '', '完成了子任务 \"Tree 的 @on-select-change 和 @on-check-change 事件返回参数新增当前项\"', 'doneChild', '2019-01-03 11:01:30', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4154, 'rchx5tm41wposf780yjz6egu', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 11:16:12', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4155, '9qnt6l7hej1s4i238pukxvmr', 'kqdcn2w40p58r31zyo6efjib', '', '重做了任务 ', 'redo', '2019-01-03 11:16:13', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4156, 'a6psv8onwmyq1e2i0g95bk3d', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 11:16:15', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4157, '6xqhe5l4pokcg0m2v7yd1tbs', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-03 11:16:18', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4158, 'lv23ua5jdypmoes7cgz89rxb', '6v7be19pwman2fird04gqu53', '增加了一个新组件 Comment', '创建了任务 ', 'create', '2019-01-03 22:25:29', 'p1aujdigrlxky76h8cs3z4w0', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4159, 'mnbz7hw2oi94u6fk3r5xqpdc', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:30', 'p1aujdigrlxky76h8cs3z4w0', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4160, 'basihz6npvt2xf71reog45ud', '6v7be19pwman2fird04gqu53', '增加了一个新组件 ConfigProvider 为组件提供统一的全局化配置', '创建了任务 ', 'create', '2019-01-03 22:25:37', '2bn918l6ejyzousa73dkpgci', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4161, '9lgmtpoj613bwevfasq4i0dk', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:37', '2bn918l6ejyzousa73dkpgci', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4162, 'z4cx5uvmfsjakr2b38w0geld', '6v7be19pwman2fird04gqu53', 'Avatar 组件增加 srcSet 属性,用于设置图片类头像响应式资源地址', '创建了任务 ', 'create', '2019-01-03 22:25:45', '3qz5hfsin69xt8cgbd70lkew', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4163, 'bde7mgz60ylicx4tap1rf82w', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:45', '3qz5hfsin69xt8cgbd70lkew', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4164, '3oktw2hblvn9f7e8mj01rduz', '6v7be19pwman2fird04gqu53', '增加 less 变量 @font-variant-base 定制 font-variant 样式', '创建了任务 ', 'create', '2019-01-03 22:25:53', 'xkic58d20srnu9jm7ohqw14f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4165, '8721mxbo0quri9jydg3halwv', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:53', 'xkic58d20srnu9jm7ohqw14f', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4166, 'cld81p5er0bsxnmj934w7qgf', '6v7be19pwman2fird04gqu53', '优化鼠标悬停在可排序的表头上时 title 的显示', '创建了任务 ', 'create', '2019-01-03 22:26:01', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4167, 'efj1xhpr9i5a0tq7y4knzub2', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:01', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4168, 'xelf9v7kzin6tj5bqoyaw1rg', '6v7be19pwman2fird04gqu53', '修正 Comment author 属性的类型为 ReactNode', '创建了任务 ', 'create', '2019-01-03 22:26:09', 'twb8f52jasn9vry6iko0dqg4', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4169, 'uiq9yv85f7zgba01w4ln6x3r', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:09', 'twb8f52jasn9vry6iko0dqg4', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4170, 'f10hn732taevsdpygjbcl5w8', '6v7be19pwman2fird04gqu53', '优化 Spin 样式并略微提升了切换状态的性能', '创建了任务 ', 'create', '2019-01-03 22:26:16', 'gjmotpbrwva079ukde4izn38', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4171, 'q237mcahkzp1sd8ij4ry6fvn', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:16', 'gjmotpbrwva079ukde4izn38', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4172, '9tc5erp8lgdxon1muhas4wyk', '6v7be19pwman2fird04gqu53', '微调 Card 头部和加载中的样式细节', '创建了任务 ', 'create', '2019-01-03 22:26:21', 'uwq87z2f0hnvrl6o9gtcb3iy', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4173, 'im06nuj924kzy3lhwqert8vp', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:21', 'uwq87z2f0hnvrl6o9gtcb3iy', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4174, 'zj73kmhg5yicqlse09v824dn', '6v7be19pwman2fird04gqu53', 'Notification 组件升级 rc-notification 到 3.3.0,增加 onClick 属性,点击通知时触发的回调函数', '更新了内容 ', 'name', '2019-01-03 22:26:36', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4175, 'ohl1jfb4wqkcad0gs76my9n5', '6v7be19pwman2fird04gqu53', 'Notification 组件升级 rc-notification 到 3.3.0', '更新了内容 ', 'name', '2019-01-03 22:26:50', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4176, 'ubrczedhjpwfv5ygnl1089s2', '6v7be19pwman2fird04gqu53', 'Cascader 升级 rc-calendar', '创建了任务 ', 'create', '2019-01-03 22:27:04', 'qug5e4alndm7930ipxwyvc2h', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4177, '71veqsi6pomnlz4fd3k85x90', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:04', 'qug5e4alndm7930ipxwyvc2h', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4178, 'fvc1z3rbgslkhwto498qpie0', '6v7be19pwman2fird04gqu53', 'Upload 组件升级 rc-upload 到 2.5.0', '创建了任务 ', 'create', '2019-01-03 22:27:17', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4179, 'a5ixq9onv1h0sm2ydr6wbult', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:17', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4180, '8jnxw6ot03yb5ms1lae9ivuq', '6v7be19pwman2fird04gqu53', '重构 Tag 组件,简化代码并提升性能', '创建了任务 ', 'create', '2019-01-03 22:27:30', 'm7u8fdp41cwrtkjxyzq2ion3', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4181, 'lo3fs4d610v98imxaertkcgw', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:30', 'm7u8fdp41cwrtkjxyzq2ion3', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4182, '2t8wh5zk3ad79rb4q0oijm1g', '6v7be19pwman2fird04gqu53', 'Badge 进行了重构,count 支持自定义组件', '创建了任务 ', 'create', '2019-01-03 22:27:36', 'jo0i8fq2579kbdgsmcw1nev4', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4183, '9mvrcztjylsghin1qwae8o67', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:36', 'jo0i8fq2579kbdgsmcw1nev4', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4184, 'phb9kdnt45ir32vojcmwl1g6', '6v7be19pwman2fird04gqu53', '重构了 Tree 底层的代码,以解决一些存在了很久的问题', '创建了任务 ', 'create', '2019-01-03 22:27:54', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4185, '3q4sxlb6jogair159w8h0u27', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:54', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4186, 'sufbt1amn9c8eqd6yil7rvx0', '6v7be19pwman2fird04gqu53', '修复了 Badge 代码错误引起的 TypeScript 类型报错', '更新了内容 ', 'name', '2019-01-03 22:28:13', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4187, 'q5lpxoh7mrzj2cbgtv0u4698', '6v7be19pwman2fird04gqu53', '修复了 Divider 与浮动元素一起使用时的样式问题', '创建了任务 ', 'create', '2019-01-03 22:28:21', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4188, 'tog9yrh7ekjc6q31fanmxwpz', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:21', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4189, 'gf0psdotxqblcy918wznaih7', '6v7be19pwman2fird04gqu53', '修复了 Form 高级搜索模式下的样式问题', '创建了任务 ', 'create', '2019-01-03 22:28:25', '0a84xkg12enqjml7rz6dbifw', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4190, 'ob51ay942rmpgk8c3vxlftdn', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:25', '0a84xkg12enqjml7rz6dbifw', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4191, 'fb9uj15xn2zs6wihpo8lmecy', '6v7be19pwman2fird04gqu53', '修复了 Upload 对无扩展名图片地址的预览展示问题', '创建了任务 ', 'create', '2019-01-03 22:28:29', 'fax4gez2jlk15tvsu3dc6p98', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4192, 'kjs527dh3qfzuolyt1i0map9', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:29', 'fax4gez2jlk15tvsu3dc6p98', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4193, 'wefjuogckp7n90qm6384bhrz', '6v7be19pwman2fird04gqu53', '修复一处 less 语法错误', '创建了任务 ', 'create', '2019-01-03 22:28:33', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4194, 'u3986mxveapdh7oj1rift2zg', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:33', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4195, 'bwtvydrazh8emo1x9up3sj27', '6v7be19pwman2fird04gqu53', '修复 LocaleProvider 中 moment.locale 调用报错的问题', '创建了任务 ', 'create', '2019-01-03 22:28:39', 'jiy25eobh1cnp7ruvg9d0m6s', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4196, 'ri6tevk1y7m2zsjwuqa45g3c', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:39', 'jiy25eobh1cnp7ruvg9d0m6s', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4197, 'ow3r8pkn7bet16x9jlh5a0d4', '6v7be19pwman2fird04gqu53', '修复 WeekPicker 的 style 属性不生效的问题', '创建了任务 ', 'create', '2019-01-03 22:28:43', '4pv9brqnm0cigwu5f3zeyxdk', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4198, 'ncagwsky8df3lm61027ore45', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:43', '4pv9brqnm0cigwu5f3zeyxdk', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4199, 'bnry5391jzg84sd72pthxlfv', '6v7be19pwman2fird04gqu53', 'Carousel: 升级 react-slick 版本以修复宽度计算错误', '创建了任务 ', 'create', '2019-01-03 22:28:50', 'td1qznl9ms65gbcfej0k4vup', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4200, 'qx8fcy1s37mo4th5kwvb69d2', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:50', 'td1qznl9ms65gbcfej0k4vup', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4201, '3d8szi6qx7t9v5ljc1yueag4', '6v7be19pwman2fird04gqu53', '修复 enterButton 的值为 button 元素时显示错误的问题', '创建了任务 ', 'create', '2019-01-03 22:28:56', 'fkrsvpzmj8xyo045hiugqt92', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4202, 'lgdti7me3ao62hxy18cpurz9', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:56', 'fkrsvpzmj8xyo045hiugqt92', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4203, '7apl461qkdgm9ui5jftsobe8', '6v7be19pwman2fird04gqu53', '修复表单校验文字消失的时候输入框会抖一下的问题', '创建了任务 ', 'create', '2019-01-03 22:29:02', '0b6wlc3754fr8gdvupx9aoys', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4204, 'gr8ibowa7mvu4k5031z9tqfe', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:29:02', '0b6wlc3754fr8gdvupx9aoys', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4205, 'ie2jn0u4dxbszq9wtyvc7r85', '6v7be19pwman2fird04gqu53', '重构了 DatePicker 相关 type 定义', '创建了任务 ', 'create', '2019-01-03 22:29:18', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4206, 'm9qkjn18zs0u3f6grpae2wo4', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:29:18', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4207, 'bxf0ydu7zcp2g5h1jaekinol', '6v7be19pwman2fird04gqu53', 'Steps 进行了重构,首次渲染的时候不会再闪烁', '创建了任务 ', 'create', '2019-01-03 22:29:24', 'hxntygarp3094c7w1856iujm', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4208, 'rq7em12fog9ulpcjiynx3zv0', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:29:24', 'hxntygarp3094c7w1856iujm', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4209, 'dkq4c2thuzwopgy8l5vrj3es', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:29:42', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4210, 'm5jgeyqv168nrzitb2xf0wuc', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-03 22:30:00', '0a84xkg12enqjml7rz6dbifw', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4211, '93auio1dnj5t7qzf0lb2kxh4', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2019-01-03 22:30:07', 'jiy25eobh1cnp7ruvg9d0m6s', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4212, 'mf4zgw5j60atlxh1pndko38s', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:30:15', 'td1qznl9ms65gbcfej0k4vup', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4213, 'l4fdhpo65mx0ji3geu7y8qsn', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2019-01-03 22:30:22', 'twb8f52jasn9vry6iko0dqg4', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4214, 'ukx9ozqe8d27cvbifrwmjy54', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:30:27', 'uwq87z2f0hnvrl6o9gtcb3iy', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4215, 'x2o3euprgi1nzf8mt70kyl6s', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-03 22:30:31', '3qz5hfsin69xt8cgbd70lkew', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4216, 'kyg8xpuovtaw2lc91765fn4j', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-03 22:30:38', 'xkic58d20srnu9jm7ohqw14f', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4217, 'a53r0dg7o2cbli4pfqewuy8m', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:30:46', 'owrs04m3e2klj8uqac6tiy17', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4218, 'r18ygstu75v4d2xewqfckhln', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:30:57', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4219, '3jxrfzquwc29yptg1bovm8i7', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:30:59', 'hxntygarp3094c7w1856iujm', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4220, '96wnyl4kut1sdxj2bzop3ivr', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:30:59', 'gjmotpbrwva079ukde4izn38', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4221, 'vmxe7qcidhsrpn3k94wb2af5', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:31:02', 'xkic58d20srnu9jm7ohqw14f', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4222, 'jf65y3tiredw8lkh1xpgzb0q', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:31:07', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4223, 'gaihuw7petn3y165lcjvrk42', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2019-01-03 22:31:13', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4224, 'w4jxovd6bsfinz0tq7rhleau', '6v7be19pwman2fird04gqu53', '
  •  I have searched the issues of this repository and believe that this is not a duplicate.

Version

3.10.9

Environment

Any every green browsers

Reproduction link

\"Edit

Steps to reproduce

Please see the linked example in \"Link to minimal reproduction\".
The hover over message on column \"Name\" is \"sort\", however, it should be \"hello\" based on the code logic.

What is expected?

When using a Popover for the title of a column, the hover over message on that column\'s header should be the value passed to Popover\'s title prop instead of \"sort\"

What is actually happening?

The hover over message on all sortable columns is \"sort\".

', '更新了备注 ', 'content', '2019-01-04 08:54:41', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4225, 'ezcbv40idx8urkswg7yoj2mq', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 紧急', 'pri', '2019-01-04 08:56:41', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4226, 'sbal18knrhvtwj0zc5g4i3dq', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月06日 18:00', 'setEndTime', '2019-01-04 08:56:45', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4227, '1b6h9dnxq27w48eygut5acmk', '6v7be19pwman2fird04gqu53', '', '添加了参与者 Chihiro', 'inviteMember', '2019-01-04 08:57:04', '6hj43ueim2bk187sqzcoy59v', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4228, 'o8khyma5vz9rbs63pdwx7iej', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 08:57:09', '6hj43ueim2bk187sqzcoy59v', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4229, '6f5g1qt4oj98ysrdhawx2c3k', '6v7be19pwman2fird04gqu53', 'this seems more like a question to @afc163 , but here\'s my two cents: it might be better to add an additional prop called something like hoverOverTitle so that it will show up when you hover over on a column header. If the consumer uses a Popover for column\'s title, the hover over behavior should honor whatever sets in that Popover component.', NULL, 'comment', '2019-01-04 08:57:35', '6hj43ueim2bk187sqzcoy59v', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4230, '90l6k8b7hngpymoacietrf1j', 'kqdcn2w40p58r31zyo6efjib', 'I want to take this and have a question,\n\nwhen hover on column Age, it should show Sort or Age?', NULL, 'comment', '2019-01-04 09:04:33', '6hj43ueim2bk187sqzcoy59v', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4231, 'fvtby5am906l87g1cxjshe32', 'y680trgedcavbhnz24u7i5m3', 'In my opinion, when type of title is string, it should show Sort, if it\'s a ReactNode, it should show the last step of prop title, maybe add prop is not a good way for user.', NULL, 'comment', '2019-01-04 09:08:23', '6hj43ueim2bk187sqzcoy59v', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4232, 'sxe2dy5wcb6hn38mig97zavq', 'y680trgedcavbhnz24u7i5m3', 'Change hover over message of Column', '创建了任务 ', 'create', '2019-01-04 09:09:28', 'gk8ipqm5406br7cwd9l1zefs', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4233, '56l3m84euzs7cypgb9qfijnv', 'y680trgedcavbhnz24u7i5m3', '', '添加了子任务 \"Change hover over message of Column\"', 'createChild', '2019-01-04 09:09:28', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4234, 'kyeuzfgnxoms658bd3tq12p7', 'y680trgedcavbhnz24u7i5m3', '', '认领了任务 ', 'claim', '2019-01-04 09:09:29', 'gk8ipqm5406br7cwd9l1zefs', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4235, '98p7g6n1ltzbfre3oxkyhv2d', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-04 09:17:51', 'm7u8fdp41cwrtkjxyzq2ion3', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4236, '8rvy3inbt46ckxu59fld7pqz', 'kqdcn2w40p58r31zyo6efjib', '新增 directory 属性,支持上传一个文件夹', '创建了任务 ', 'create', '2019-01-04 09:18:18', 'o61b3s24exmcy8njkparwthd', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4237, 'nkr94lumsqbgi2ofhp7d8c5x', 'kqdcn2w40p58r31zyo6efjib', '', '添加了子任务 \"新增 directory 属性,支持上传一个文件夹\"', 'createChild', '2019-01-04 09:18:18', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4238, 'zy5t9lmv671r4qxf8bpcwjsn', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-04 09:18:18', 'o61b3s24exmcy8njkparwthd', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4239, 'yp58fhjvoxanqbtirwz92m3c', 'kqdcn2w40p58r31zyo6efjib', 'action 属性支持作为一个返回 Promise 对象的函数,使用更加灵活', '创建了任务 ', 'create', '2019-01-04 09:18:24', 'orycwlhf7n2qx1pta038dzjk', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4240, 'sm4bhc5v2fqo70ja6twrzyuk', 'kqdcn2w40p58r31zyo6efjib', '', '添加了子任务 \"action 属性支持作为一个返回 Promise 对象的函数,使用更加灵活\"', 'createChild', '2019-01-04 09:18:24', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4241, 'e9k6u3vtrwb48njmzhgipcxl', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-04 09:18:24', 'orycwlhf7n2qx1pta038dzjk', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4242, 'e8o2chmva49yb670g5ld1n3j', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-04 09:18:50', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4243, 'yxv4b1kd0zntahm63sfj2cpi', 'kqdcn2w40p58r31zyo6efjib', '', '更新任务优先级为 非常紧急', 'pri', '2019-01-04 09:19:16', '3qz5hfsin69xt8cgbd70lkew', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4244, 'gxq2b46fh7t8rudnvcsimeaz', 'kqdcn2w40p58r31zyo6efjib', '', '更新截止时间为 01月04日 18:00', 'setEndTime', '2019-01-04 09:19:42', '0a84xkg12enqjml7rz6dbifw', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4245, '053rfobwtegvy7mnq4s8k1c9', 'kqdcn2w40p58r31zyo6efjib', '', '更新截止时间为 01月01日 18:00', 'setEndTime', '2019-01-04 09:19:51', 'p1aujdigrlxky76h8cs3z4w0', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4246, 'dyj9z6wvmrh84bx07ofsacnq', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 Chihiro', 'assign', '2019-01-04 09:19:58', 'p1aujdigrlxky76h8cs3z4w0', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4247, 'usbyo9z8x12frq7lgmvp0tk3', 'y680trgedcavbhnz24u7i5m3', '', '完成了任务 ', 'done', '2019-01-04 09:23:11', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4248, 'undfg6m57a1wkojy0i9l4bcq', 'y680trgedcavbhnz24u7i5m3', '', '重做了任务 ', 'redo', '2019-01-04 09:23:12', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4249, 'u5j06pz2gqky9m8341aervnc', '6v7be19pwman2fird04gqu53', 'Add variant prop and deprecate fullWidth and scrollable props', '创建了任务 ', 'create', '2019-01-04 21:17:13', 'up6hn9bd34c8mglwaj1ytefz', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4250, 'hv9jscyrzu6fb5q1xpnm7wdl', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:17:13', 'up6hn9bd34c8mglwaj1ytefz', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4251, 'ecb7pyij8q9w42tkfgh3s5xv', '6v7be19pwman2fird04gqu53', 'Add styles to make size property work with extended property', '创建了任务 ', 'create', '2019-01-04 21:17:19', 'krj4p7ix2cf605vyltmudq1e', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4252, 'smkh91f8r4p5ayjqxnl2utgz', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:17:19', 'krj4p7ix2cf605vyltmudq1e', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4253, 'vi0nwtrzx62bqluc9hd1pf73', '6v7be19pwman2fird04gqu53', 'Add cross references from Modal docs to other components', '创建了任务 ', 'create', '2019-01-04 21:17:46', '1g3vc8tkyla20fp5rdhxe7mo', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4254, 'h18qzjuxwfsrtck5gb37dey2', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:17:46', '1g3vc8tkyla20fp5rdhxe7mo', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4255, 'lnd73yz9tp4sha8mgc6jbofx', '6v7be19pwman2fird04gqu53', 'Add createSvgIcon type definition', '创建了任务 ', 'create', '2019-01-04 21:18:25', 'nqrleu2c90zsdaj1yph4m8bt', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4256, '69fd13ekwabg072incpjtzxq', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:18:25', 'nqrleu2c90zsdaj1yph4m8bt', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4257, 'fwcql690r7pd2jmoit53h1sy', '6v7be19pwman2fird04gqu53', 'Add customized demo', '创建了任务 ', 'create', '2019-01-04 21:18:37', 'mix3cg2eh1u60fknd7yz9v5t', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4258, 'o7xejsdzrcygia48bflh2mn9', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:18:37', 'mix3cg2eh1u60fknd7yz9v5t', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4259, 'jibgsz3vh728qpxdnywtalmc', '6v7be19pwman2fird04gqu53', 'Add defaultTheme option for makeStyles', '创建了任务 ', 'create', '2019-01-04 21:18:45', 'dckxz1vpujtafshgr20mwo7e', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4260, 'i1d3zsf5hb4ujwvcy6lp09rg', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:18:45', 'dckxz1vpujtafshgr20mwo7e', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4261, 'faneshd15vcx782mpo4qjrz0', '6v7be19pwman2fird04gqu53', 'Add nextjs-hooks-with-typescript', '创建了任务 ', 'create', '2019-01-04 21:18:53', 'fd1avskez2q43w80xhb7ypc9', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4262, 'jxfa2oiru6by80lg3hcv4s9q', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:18:53', 'fd1avskez2q43w80xhb7ypc9', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4263, '036qie8nhvp1wmjs4bfgkua5', '6v7be19pwman2fird04gqu53', 'Add note on archived components', '创建了任务 ', 'create', '2019-01-04 21:19:01', 'as2y4r6mwxuhgvncop3f8z90', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4264, 'fjb157v0mexsh8qwig2ay3n4', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:19:01', 'as2y4r6mwxuhgvncop3f8z90', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4265, 'q7majphuec3wlbyig9r4kons', '6v7be19pwman2fird04gqu53', 'Add Instagram theme', '创建了任务 ', 'create', '2019-01-04 21:19:08', '8zj3vpx0b7qud24ylfgces1m', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4266, 'egxlqbr1d27p49yafkv3cn0u', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:19:08', '8zj3vpx0b7qud24ylfgces1m', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4267, 'ebpjx21r0yhkwcsm59gn3auo', '6v7be19pwman2fird04gqu53', 'Add component prop', '创建了任务 ', 'create', '2019-01-04 21:19:18', 'hcrdvbuzwgojst2f0p134qxi', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4268, 'kd3bmhtc0nwi9ojeu2axgqrz', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:19:18', 'hcrdvbuzwgojst2f0p134qxi', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4269, 'af0puzmxh5dvi1yr8c2q96kw', '6v7be19pwman2fird04gqu53', 'Fix utils.chainPropTypes issue', '创建了任务 ', 'create', '2019-01-04 21:19:37', 'lmognshqz21dbewcu9a3rx87', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4270, 'subp5hco4z30x8ml629wtv7a', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:19:37', 'lmognshqz21dbewcu9a3rx87', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4271, 'ig7evacluxktjm865oy0241w', '6v7be19pwman2fird04gqu53', 'Fix vertical text alignment by reducing padding', '创建了任务 ', 'create', '2019-01-04 21:19:51', 'n6ulc7ebxpqahi50dy9k1sgf', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4272, 'c734zkl1hs9ef5p8vmgrow2y', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:19:51', 'n6ulc7ebxpqahi50dy9k1sgf', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4273, 'blspvm6321oqxw5ekd8fgrzh', '6v7be19pwman2fird04gqu53', 'Fix infinite loop in the scroll button logic', '创建了任务 ', 'create', '2019-01-04 21:19:57', 'rqjng1kfcp4wyiamt6o23zbu', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4274, 'ce7qy51oun6xsvgjz8lt29wp', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:19:57', 'rqjng1kfcp4wyiamt6o23zbu', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4275, 'pjo8kwxfz0t4s9riqc1a2y56', '6v7be19pwman2fird04gqu53', 'Fix component animations', '创建了任务 ', 'create', '2019-01-04 21:20:05', 'qsz65fvgi8hyx3e7bn14o9wm', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4276, 'wyx6dmp8aknejcf53ihu1ob9', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:20:05', 'qsz65fvgi8hyx3e7bn14o9wm', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4277, '4rdmf20pteglk9nsyqi3jb85', '6v7be19pwman2fird04gqu53', 'Fix responsivePropType typo', '创建了任务 ', 'create', '2019-01-04 21:20:12', 'byiuxhn0v6sod4zap1t2fclr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4278, '48o1z0emd6chu29xk5iwansf', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:20:12', 'byiuxhn0v6sod4zap1t2fclr', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4279, 'srabgivxch13kuemzq9n58to', '6v7be19pwman2fird04gqu53', 'Change action element to have a fixed right margin', '创建了任务 ', 'create', '2019-01-04 21:20:27', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4280, 'z82lho53je6sfc7r9i0dnwpa', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:20:27', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4281, 'lti2ksapfd06rux3w5q9zgvc', '6v7be19pwman2fird04gqu53', 'Change height from 5 to 4 pixels', '创建了任务 ', 'create', '2019-01-04 21:20:33', 'vmzeciodgbfp7ysu38tq10kj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4282, 'pkwhqt1ncf5o6xyjmu42irgb', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:20:33', 'vmzeciodgbfp7ysu38tq10kj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4283, 'fcp6rwx0gklq8a7hz9b35ijd', '6v7be19pwman2fird04gqu53', 'Change sub-components to have fixed gutters', '创建了任务 ', 'create', '2019-01-04 21:20:45', '6cagd725tifonvw0qphe9zsb', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4284, '4fwn60cymibsg58vaxoleq37', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:20:45', '6cagd725tifonvw0qphe9zsb', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4285, '1ln8e40zfx6ok7jabdqcpr29', '6v7be19pwman2fird04gqu53', 'Change the classes structure to match the core components convention', '创建了任务 ', 'create', '2019-01-04 21:20:54', 'xu3jgyow2s9f1km0rctqin4v', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4286, 'jpwcd12ve5zgiq6ofu9r8atl', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:20:54', 'xu3jgyow2s9f1km0rctqin4v', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4287, 'protl2wye4km9zns3bq87icx', '6v7be19pwman2fird04gqu53', 'Update the action spacing to better match the spec', '创建了任务 ', 'create', '2019-01-04 21:21:12', 'k3g07m1qyctvbp95siohju6f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4288, 'dayz0l5kr98s1fpnj7ohm623', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:21:12', 'k3g07m1qyctvbp95siohju6f', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4289, '038psj6uflqcn9y5ravhtbgd', '6v7be19pwman2fird04gqu53', 'Update the emotion documentation', '创建了任务 ', 'create', '2019-01-04 21:21:18', 'oh5wpj9kd8e6ltusxq271ma3', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4290, 'v7l5xz63qmy2sfc0igub18wp', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:21:18', 'oh5wpj9kd8e6ltusxq271ma3', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4291, 'ikpveu0dxl2fgrna16ms3tyo', '6v7be19pwman2fird04gqu53', 'Update the CodeFund embed script', '创建了任务 ', 'create', '2019-01-04 21:21:25', 'akdwslbtp3z82xecui0y4ovq', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4292, 'q1p8igck4a9nxo3l502d7h6z', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:21:25', 'akdwslbtp3z82xecui0y4ovq', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4293, 'y2unpg9qifrm14kd3x8tzvs6', '6v7be19pwman2fird04gqu53', 'Update react-select demo to have isClearable set to true', '创建了任务 ', 'create', '2019-01-04 21:21:31', 'hayfr6vl398nq5exgszobu2j', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4294, '75ecai2omdzqurl3jpy9w0th', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:21:31', 'hayfr6vl398nq5exgszobu2j', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4295, 'u910zdfm24ji3hoqstwnyvl7', '6v7be19pwman2fird04gqu53', 'Update album page-layout preview image album.png', '创建了任务 ', 'create', '2019-01-04 21:21:41', 'mf80iu15kepavbg2r9ldcjsh', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4296, '91gkpfrqeclbu5h8xdzn40mj', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:21:42', 'mf80iu15kepavbg2r9ldcjsh', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4297, 'ksw9y1dmvntue7o85q2xplig', '6v7be19pwman2fird04gqu53', 'Update some components to better match the Material specification', '创建了任务 ', 'create', '2019-01-04 21:22:08', 'nzy71f5i6g0skwau4lrj3d8b', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4298, 'jxbdmz1csi2e3w7aouvfnlrg', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:22:08', 'nzy71f5i6g0skwau4lrj3d8b', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4299, 'wx6fm0c9iea3qlvb7ounksd2', '6v7be19pwman2fird04gqu53', 'Remove hoisting of static properties in HOCs', '创建了任务 ', 'create', '2019-01-04 21:22:20', '4cug3e5rodalq9x81ywht0zn', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4300, 'zi16cj4gmyn9wfb72ktroxa0', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:22:20', '4cug3e5rodalq9x81ywht0zn', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4301, 'me0stqcvz5r48p29nyu76x1a', '6v7be19pwman2fird04gqu53', 'Remove the withRoot HOC', '创建了任务 ', 'create', '2019-01-04 21:22:24', '92fow0le47htb6xkv5ynzuri', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4302, 'rmce81xwt5f9sb7uv6o0k3yn', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:22:24', '92fow0le47htb6xkv5ynzuri', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4303, 'lfeou15n7gzktw8xrdmqayh6', '6v7be19pwman2fird04gqu53', '100% remove the prop types', '创建了任务 ', 'create', '2019-01-04 21:22:34', '6ky18i9cg0eqvfzn2th3ux5l', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4304, '5o7unx3qt62s4impw8hyzl0k', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:22:34', '6ky18i9cg0eqvfzn2th3ux5l', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4305, 'r6j9of80pmdlatq2b5wzsx7i', '6v7be19pwman2fird04gqu53', 'Remove unused lint directives', '创建了任务 ', 'create', '2019-01-04 21:22:40', 'zj6skt9orn748gh5mvb2ueif', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4306, 'i3zehaf9ux1bpjqsmgl2w4kt', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:22:40', 'zj6skt9orn748gh5mvb2ueif', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4307, 'itoehcalwm75x86ndukgvbz9', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 非常紧急', 'pri', '2019-01-04 21:23:11', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4308, 'a60nuwtb2skd7mqpi5vroc89', '6v7be19pwman2fird04gqu53', 'If you install react-jss via npm, currently it installs with version 10.0.0-alpha.7 which has breaking changes included.\nSo I guess it should be updated with below? Correct me if I\'m wrong, thanks', NULL, 'comment', '2019-01-04 21:27:17', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4309, 'zv57agjqeymuw4tpkilrs0h1', 'kqdcn2w40p58r31zyo6efjib', 'The new JSS v10 alpha seems to have deprecated the react-jss library, or at least the github repo, and there seem to be breaking API changes as well, the migration path of which is not clear.', NULL, 'comment', '2019-01-04 21:27:30', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4310, '3lwyhv5tf4d2ec7a1qknp6zr', 'y680trgedcavbhnz24u7i5m3', 'yeah, npm tags is a mess right now, need to find a way to set them each time with our current setup over lerna publish or drop lerna and do it differently', NULL, 'comment', '2019-01-04 21:29:15', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4312, 'jbex46ci20y781aswvgprdhm', 'y680trgedcavbhnz24u7i5m3', 'Improve demos loading', '创建了任务 ', 'create', '2019-01-04 21:30:13', 'a75dcqx2sjivokmg49yh380l', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4314, 'kt72j1eobvif5hdpzcmqxgy9', 'y680trgedcavbhnz24u7i5m3', '', '认领了任务 ', 'claim', '2019-01-04 21:30:13', 'a75dcqx2sjivokmg49yh380l', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4315, '0ycs8l7j123w4dgpoetka9qu', 'y680trgedcavbhnz24u7i5m3', 'Improve the service-worker logic', '创建了任务 ', 'create', '2019-01-04 21:30:19', '7ns924ofulpjxkgq06y3bm5r', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4317, 'g3yznvlot84umieabq01p5ck', 'y680trgedcavbhnz24u7i5m3', '', '认领了任务 ', 'claim', '2019-01-04 21:30:19', '7ns924ofulpjxkgq06y3bm5r', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4319, '2qwgit8mpduzsanbckrvx97e', 'y680trgedcavbhnz24u7i5m3', '', '完成了任务 ', 'done', '2019-01-04 21:31:17', 'fd1avskez2q43w80xhb7ypc9', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4320, '0brwcgnltfmhxipd78aos542', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-04 21:31:23', 'nzy71f5i6g0skwau4lrj3d8b', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4321, 'hwv5j3mqod1gntape8s62yiu', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-04 21:31:27', 'qsz65fvgi8hyx3e7bn14o9wm', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4322, 'vicyxaq4ptu21r8gjs9benmz', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-04 21:31:47', '1g3vc8tkyla20fp5rdhxe7mo', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4323, 'se0zp1hf792rcmbx8l4dyg5i', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月02日 18:00', 'setEndTime', '2019-01-04 21:32:38', '4cug3e5rodalq9x81ywht0zn', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4325, 'r9qeij67bmxh31na2slkco0z', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 紧急', 'pri', '2019-01-04 21:33:45', 'zj6skt9orn748gh5mvb2ueif', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4326, 'ch920iglvuz6pdreb7wyf4a8', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月05日 18:00', 'setEndTime', '2019-01-04 21:33:55', 'krj4p7ix2cf605vyltmudq1e', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4328, 'dbve2z5hqowgut1c69rai74p', '6v7be19pwman2fird04gqu53', '', '关联了文件 ioncube_loaders_win_nonts_vc11_x86.zip', 'linkFile', '2019-01-11 10:45:40', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'link'); +INSERT INTO `pear_project_log` VALUES (4329, '6lbv9wakfts5jd4eyhgco2ur', '6v7be19pwman2fird04gqu53', '', '关联了文件 技术部项目周报.xlsx', 'linkFile', '2019-01-11 10:45:40', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'link'); +INSERT INTO `pear_project_log` VALUES (4330, 'm6847e9oj20tagqb5uclfyir', '6v7be19pwman2fird04gqu53', '', '关联了文件 cover.png', 'linkFile', '2019-01-11 10:46:07', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'link'); +INSERT INTO `pear_project_log` VALUES (4331, 'cguhle3aypqw05fz6iv4rmjn', '6v7be19pwman2fird04gqu53', '', '取消关联文件 cover.png', 'unlinkFile', '2019-01-11 11:19:16', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'disconnect'); +INSERT INTO `pear_project_log` VALUES (4332, 'ibq97pevg4w6tfna3hukydjr', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2019-01-12 22:27:56', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4333, 'te280dxhycfkjzvqwi9bam63', '6v7be19pwman2fird04gqu53', '', '恢复了任务 ', 'recovery', '2019-01-13 20:46:29', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'undo'); + +-- ---------------------------- +-- Table structure for pear_project_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_member`; +CREATE TABLE `pear_project_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '项目id', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `join_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加入时间', + `is_owner` int(11) NULL DEFAULT 0 COMMENT '拥有者', + `authorize` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目-成员表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_member +-- ---------------------------- +INSERT INTO `pear_project_member` VALUES (1, 'a8mpr6tvbndk10hj2lwcqzuo', '6v7be19pwman2fird04gqu53', NULL, 1, NULL); +INSERT INTO `pear_project_member` VALUES (2, 'a8mpr6tvbndk10hj2lwcqzuo', 'kqdcn2w40p58r31zyo6efjib', NULL, 0, NULL); +INSERT INTO `pear_project_member` VALUES (6, 'a8mpr6tvbndk10hj2lwcqzuo', 'y680trgedcavbhnz24u7i5m3', '2018-12-23 08:24:29', 0, NULL); +INSERT INTO `pear_project_member` VALUES (7, '8rlqyh56smzpoc1wef7390t2', '6v7be19pwman2fird04gqu53', '2018-12-23 08:25:24', 1, NULL); +INSERT INTO `pear_project_member` VALUES (8, '8rlqyh56smzpoc1wef7390t2', 'kqdcn2w40p58r31zyo6efjib', '2018-12-23 08:25:28', 1, NULL); +INSERT INTO `pear_project_member` VALUES (9, 'nkp4gulsb6oxqyi80fhead39', '6v7be19pwman2fird04gqu53', '2018-12-23 08:26:20', 1, NULL); +INSERT INTO `pear_project_member` VALUES (10, 'sbklfvyouc0qpmwhitn47j5z', '6v7be19pwman2fird04gqu53', '2018-12-23 08:26:24', 1, NULL); +INSERT INTO `pear_project_member` VALUES (11, 'n5opgqevrz1l03h48uwx67d2', '6v7be19pwman2fird04gqu53', '2018-12-23 08:26:31', 1, NULL); +INSERT INTO `pear_project_member` VALUES (12, 'tnxpbov8kez6m4wl2hfjucd9', '6v7be19pwman2fird04gqu53', '2018-12-23 08:31:53', 1, NULL); +INSERT INTO `pear_project_member` VALUES (16, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2018-12-25 07:20:36', 1, NULL); +INSERT INTO `pear_project_member` VALUES (20, 'mo4uqwfb06dxv8ez2spkl3rg', 'y680trgedcavbhnz24u7i5m3', '2018-12-27 12:04:03', 0, NULL); +INSERT INTO `pear_project_member` VALUES (21, 'ibag9hw3o1tusd5qlpxrk782', 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:02:14', 1, NULL); +INSERT INTO `pear_project_member` VALUES (23, 'p94ckbwv5lyxt2rhzeam3s86', '582', '2019-01-02 11:17:27', 1, NULL); +INSERT INTO `pear_project_member` VALUES (24, '8ulzfth64cd0k1x5peivowm2', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 09:15:11', 1, NULL); +INSERT INTO `pear_project_member` VALUES (25, '8ulzfth64cd0k1x5peivowm2', '6v7be19pwman2fird04gqu53', '2019-01-03 10:51:36', 0, NULL); +INSERT INTO `pear_project_member` VALUES (26, '8ulzfth64cd0k1x5peivowm2', 'y680trgedcavbhnz24u7i5m3', '2019-01-03 10:54:17', 0, NULL); +INSERT INTO `pear_project_member` VALUES (28, 'mo4uqwfb06dxv8ez2spkl3rg', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:29:57', 0, NULL); +INSERT INTO `pear_project_member` VALUES (29, 'elqa703jyvfhpt1dsxkzi8on', 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:17:34', 1, NULL); +INSERT INTO `pear_project_member` VALUES (30, 'elqa703jyvfhpt1dsxkzi8on', 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:17:38', 0, NULL); +INSERT INTO `pear_project_member` VALUES (31, 'ibag9hw3o1tusd5qlpxrk782', '6v7be19pwman2fird04gqu53', '2019-01-04 21:45:41', 0, NULL); +INSERT INTO `pear_project_member` VALUES (32, 'gbim9jpevkh7qr6ufa1t3wl4', 'vys8gd32cfui6brtwzj4pqho', '2019-01-05 21:57:31', 1, NULL); +INSERT INTO `pear_project_member` VALUES (33, 'gbim9jpevkh7qr6ufa1t3wl4', 'kqdcn2w40p58r31zyo6efjib', '2019-01-05 21:57:36', 0, NULL); + +-- ---------------------------- +-- Table structure for pear_project_menu +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_menu`; +CREATE TABLE `pear_project_menu` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `pid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '父id', + `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '名称', + `icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '菜单图标', + `url` varchar(400) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '链接', + `file_path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件路径', + `params` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '链接参数', + `node` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '#' COMMENT '权限节点', + `sort` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '菜单排序', + `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态(0:禁用,1:启用)', + `create_by` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `is_inner` tinyint(1) NULL DEFAULT 0 COMMENT '是否内页', + `values` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数默认值', + `show_slider` tinyint(1) NULL DEFAULT 1 COMMENT '是否显示侧栏', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 163 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目菜单表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_menu +-- ---------------------------- +INSERT INTO `pear_project_menu` VALUES (120, 0, '工作台', 'appstore-o', 'home', 'home', '', '#', 0, 1, 0, '2018-09-30 16:30:01', 0, '', 0); +INSERT INTO `pear_project_menu` VALUES (121, 0, '项目管理', 'project', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (122, 121, '项目列表', 'branches', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (124, 0, '系统设置', 'setting', '#', '#', '', '#', 100, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (125, 124, '成员管理', 'unlock', '#', '#', '', '#', 10, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (126, 125, '账号列表', '', 'system/account', 'system/account', '', 'project/account/index', 10, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (127, 122, '我的组织', '', 'organization', 'organization', '', 'project/organization/index', 30, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (130, 125, '访问授权', '', 'system/account/auth', 'system/account/auth', '', 'project/auth/index', 20, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (131, 125, '授权页面', '', 'system/account/apply', 'system/account/apply', ':id', 'project/auth/apply', 30, 1, 0, '0000-00-00 00:00:00', 1, '', 1); +INSERT INTO `pear_project_menu` VALUES (138, 121, '消息提醒', 'info-circle-o', '#', '#', '', '#', 30, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (139, 138, '站内消息', '', 'notify/notice', 'notify/notice', '', 'project/notify/index', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (140, 138, '系统公告', '', 'notify/system', 'notify/system', '', 'project/notify/index', 10, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (143, 124, '系统管理', 'appstore', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (144, 143, '菜单路由', '', 'system/config/menu', 'system/config/menu', '', 'project/menu/menuadd', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (145, 143, '访问节点', '', 'system/config/node', 'system/config/node', '', 'project/node/save', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (148, 124, '个人管理', 'user', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (149, 148, '个人设置', '', 'account/setting/base', 'account/setting/base', '', 'project/index/editpersonal', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (150, 148, '安全设置', '', 'account/setting/security', 'account/setting/security', '', 'project/index/editpersonal', 0, 1, 0, '0000-00-00 00:00:00', 1, '', 1); +INSERT INTO `pear_project_menu` VALUES (151, 122, '我的项目', '', 'project/list', 'project/list', ':type', 'project/project/index', 0, 1, 0, '0000-00-00 00:00:00', 0, 'my', 1); +INSERT INTO `pear_project_menu` VALUES (152, 122, '回收站', '', 'project/recycle', 'project/recycle', '', 'project/project/index', 20, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (153, 121, '项目空间', 'heat-map', 'project/space/task', 'project/space/task', ':code', '#', 20, 1, 0, '0000-00-00 00:00:00', 1, '', 1); +INSERT INTO `pear_project_menu` VALUES (154, 153, '任务详情', '', 'project/space/task/:code/detail', 'project/space/taskdetail', ':code', 'project/task/read', 0, 1, 0, '0000-00-00 00:00:00', 1, '', 0); +INSERT INTO `pear_project_menu` VALUES (155, 122, '我的收藏', '', 'project/list', 'project/list', ':type', 'project/project/index', 0, 1, 0, '0000-00-00 00:00:00', 0, 'collect', 1); +INSERT INTO `pear_project_menu` VALUES (156, 121, '基础设置', 'experiment', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (157, 156, '项目模板', '', 'project/template', 'project/template', '', 'project/project_template/index', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (158, 156, '项目列表模板', '', 'project/template/taskStages', 'project/template/taskStages', ':code', 'project/task_stages_template/index', 0, 1, 0, '0000-00-00 00:00:00', 1, '', 0); +INSERT INTO `pear_project_menu` VALUES (159, 122, '已归档项目', '', 'project/archive', 'project/archive', '', 'project/project/index', 10, 1, 0, NULL, 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (160, 0, '团队成员', 'team', 'members', 'members', '', 'project/account/index', 0, 1, 0, NULL, 0, '', 0); +INSERT INTO `pear_project_menu` VALUES (161, 153, '项目概况', '', 'project/space/overview', 'project/space/overview', ':code', 'project/index/info', 20, 1, 0, NULL, 1, '', 0); +INSERT INTO `pear_project_menu` VALUES (162, 153, '项目文件', '', 'project/space/files', 'project/space/files', ':code', 'project/index/info', 10, 1, 0, NULL, 1, '', 0); + +-- ---------------------------- +-- Table structure for pear_project_node +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_node`; +CREATE TABLE `pear_project_node` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `node` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '节点代码', + `title` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '节点标题', + `is_menu` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '是否可设置为菜单', + `is_auth` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '是否启动RBAC权限控制', + `is_login` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '是否启动登录控制', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_system_node_node`(`node`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 603 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目端节点表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_node +-- ---------------------------- +INSERT INTO `pear_project_node` VALUES (360, 'project', '项目管理模块', 0, 1, 1, '2018-09-19 17:48:16'); +INSERT INTO `pear_project_node` VALUES (361, 'project/index/info', '详情', 0, 0, 1, '2018-09-19 17:48:34'); +INSERT INTO `pear_project_node` VALUES (362, 'project/index', '基础版块', 0, 1, 1, '2018-09-19 17:48:34'); +INSERT INTO `pear_project_node` VALUES (363, 'project/index/index', '框架布局', 0, 0, 1, '2018-09-30 16:48:35'); +INSERT INTO `pear_project_node` VALUES (364, 'project/index/systemconfig', '系统信息', 0, 0, 0, '2018-09-30 16:55:11'); +INSERT INTO `pear_project_node` VALUES (365, 'project/index/editpersonal', '修改个人资料', 0, 0, 1, '2018-09-30 17:42:42'); +INSERT INTO `pear_project_node` VALUES (366, 'project/index/uploadavatar', '上传头像', 0, 0, 1, '2018-09-30 17:42:46'); +INSERT INTO `pear_project_node` VALUES (370, 'project/account', '账号管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (371, 'project/account/index', '账号列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (372, 'project/organization/index', '组织列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (373, 'project/organization/save', '创建组织', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (374, 'project/organization/read', '组织信息', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (375, 'project/organization/edit', '编辑组织', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (376, 'project/organization/delete', '删除组织', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (377, 'project/organization', '组织管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (388, 'project/auth/index', '权限列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (389, 'project/auth/add', '添加权限角色', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (390, 'project/auth/edit', '编辑权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (391, 'project/auth/forbid', '禁用权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (392, 'project/auth/resume', '启用权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (393, 'project/auth/del', '删除权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (394, 'project/auth', '访问授权', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (395, 'project/auth/apply', '应用权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (396, 'project/notify/index', '通知列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (397, 'project/notify/noreads', '未读通知', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (399, 'project/notify/read', '通知信息', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (401, 'project/notify/delete', '删除通知', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (402, 'project/notify', '通知管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (434, 'project/account/auth', '授权管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (435, 'project/account/add', '添加账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (436, 'project/account/edit', '编辑账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (437, 'project/account/del', '删除账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (438, 'project/account/forbid', '禁用账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (439, 'project/account/resume', '启用账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (498, 'project/notify/setreadied', '设置已读', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (499, 'project/notify/batchdel', '批量删除', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (500, 'project/auth/setdefault', '设置默认权限', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (501, 'project/department', '部门管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (502, 'project/department/index', '部门列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (503, 'project/department/read', '部门信息', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (504, 'project/department/save', '创建部门', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (505, 'project/department/edit', '编辑部门', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (506, 'project/department/delete', '删除部门', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (507, 'project/department_member', '部门成员管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (508, 'project/department_member/index', '部门成员列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (509, 'project/department_member/searchinvitemember', '搜索部门成员', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (510, 'project/department_member/invitemember', '添加部门成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (511, 'project/department_member/removemember', '移除部门成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (512, 'project/index/changecurrentorganization', '切换当前组织', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (513, 'project/index/editpassword', '修改密码', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (514, 'project/index/uploadimg', '上传图片', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (515, 'project/menu', '菜单管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (516, 'project/menu/menu', '菜单列表', 0, 0, 0, NULL); +INSERT INTO `pear_project_node` VALUES (517, 'project/menu/menuadd', '添加菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (518, 'project/menu/menuedit', '编辑菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (519, 'project/menu/menuforbid', '禁用菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (520, 'project/menu/menuresume', '启用菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (521, 'project/menu/menudel', '删除菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (522, 'project/node', '节点管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (523, 'project/node/index', '节点列表', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (524, 'project/node/alllist', '全部节点列表', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (525, 'project/node/clear', '清理节点', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (526, 'project/node/save', '编辑节点', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (527, 'project/project', '项目管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (528, 'project/project/index', '项目列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (529, 'project/project/selflist', '个人项目列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (530, 'project/project/save', '创建项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (531, 'project/project/read', '项目信息', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (532, 'project/project/edit', '编辑项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (533, 'project/project/uploadcover', '上传项目封面', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (534, 'project/project/recycle', '项目放入回收站', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (535, 'project/project/recovery', '恢复项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (536, 'project/project/archive', '归档项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (537, 'project/project/recoveryarchive', '取消归档项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (538, 'project/project/quit', '退出项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (539, 'project/project_collect', '项目收藏管理', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (540, 'project/project_collect/collect', '收藏项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (541, 'project/project_member', '项目成员管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (542, 'project/project_member/index', '项目成员列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (543, 'project/project_member/searchinvitemember', '搜索项目成员', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (544, 'project/project_member/invitemember', '邀请项目成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (545, 'project/project_template', '项目模板管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (546, 'project/project_template/index', '项目模板列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (547, 'project/project_template/save', '创建项目模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (548, 'project/project_template/uploadcover', '上传项目模板封面', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (549, 'project/project_template/edit', '编辑项目模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (550, 'project/project_template/delete', '删除项目模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (551, 'project/task/index', '任务列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (552, 'project/task/selflist', '个人任务列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (553, 'project/task/read', '任务信息', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (554, 'project/task/save', '创建任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (555, 'project/task/taskdone', '更改任务状态', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (556, 'project/task/assigntask', '指派任务执行者', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (557, 'project/task/sort', '任务排序', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (558, 'project/task/createcomment', '发表任务评论', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (559, 'project/task/edit', '编辑任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (560, 'project/task/like', '点赞任务', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (561, 'project/task/star', '收藏任务', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (562, 'project/task/recycle', '移动任务到回收站', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (563, 'project/task/recovery', '恢复任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (564, 'project/task/delete', '删除任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (565, 'project/task', '任务管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (569, 'project/task_member/index', '任务成员列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (570, 'project/task_member/searchinvitemember', '搜索任务成员', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (571, 'project/task_member/invitemember', '添加任务成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (572, 'project/task_member/invitememberbatch', '批量添加任务成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (573, 'project/task_member', '任务成员管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (574, 'project/task_stages', '任务分组管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (575, 'project/task_stages/index', '任务分组列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (576, 'project/task_stages/tasks', '任务分组任务列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (577, 'project/task_stages/sort', '任务分组排序', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (578, 'project/task_stages/save', '添加任务分组', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (579, 'project/task_stages/edit', '编辑任务分组', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (580, 'project/task_stages/delete', '删除任务分组', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (581, 'project/task_stages_template/index', '任务分组模板列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (582, 'project/task_stages_template/save', '创建任务分组模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (583, 'project/task_stages_template/edit', '编辑任务分组模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (584, 'project/task_stages_template/delete', '删除任务分组模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (585, 'project/task_stages_template', '任务分组模板管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (587, 'project/project_member/removemember', '移除项目成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (588, 'project/task/datetotalforproject', '任务统计', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (589, 'project/task/tasksources', '任务资源列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (590, 'project/file', '文件管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (591, 'project/file/index', '文件列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (592, 'project/file/read', '文件详情', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (593, 'project/file/uploadfiles', '上传文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (594, 'project/file/edit', '编辑文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (595, 'project/file/recycle', '文件移至回收站', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (596, 'project/file/recovery', '恢复文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (597, 'project/file/delete', '删除文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (598, 'project/project/getlogbyselfproject', '项目概况', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (599, 'project/source_link', '资源关联管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (600, 'project/source_link/delete', '取消关联', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (601, 'project/task/tasklog', '任务动态', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (602, 'project/task/recyclebatch', '批量移动任务到回收站', 0, 1, 1, NULL); + +-- ---------------------------- +-- Table structure for pear_project_template +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_template`; +CREATE TABLE `pear_project_template` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `sort` tinyint(2) NULL DEFAULT 0, + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `organization_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '组织id', + `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面', + `member_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建人', + `is_system` tinyint(1) NULL DEFAULT 0 COMMENT '系统默认', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '项目类型表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_template +-- ---------------------------- +INSERT INTO `pear_project_template` VALUES (11, '产品进展', '适用于互联网产品人员对产品计划、跟进及发布管理', 0, '2018-04-30 22:15:10', 'd85f1bvwpml2nhxe94zu7tyi', '6v7be19pwman2fird04gqu53', 'http://easyproject.net/static/image/default/cover.png', '', 1); +INSERT INTO `pear_project_template` VALUES (12, '需求管理', '适用于产品部门对需求的收集、评估及反馈管理', 0, '2018-04-30 22:16:29', 'd85f1bvwpml2nhxe92zu7tyi', '6v7be19pwman2fird04gqu53', 'http://easyproject.net/static/image/default/cover.png', '', 1); +INSERT INTO `pear_project_template` VALUES (13, '机械制造', '适用于制造商对图纸设计及制造安装的工作流程管理', 0, '2018-04-30 22:19:06', 'd85f1bvwpml2nhxe91zu7tyi', '6v7be19pwman2fird04gqu53', 'http://easyproject.net/static/image/default/cover.png', '', 1); +INSERT INTO `pear_project_template` VALUES (19, 'OKR 管理', '适用于团队的 OKR 管理', 0, '2018-12-24 16:57:49', 'un6125mxt4dcizhjqwvgyb3a', '6v7be19pwman2fird04gqu53', 'https://beta.vilson.xyz/static/upload//20190103/4c46f35da98ca0e1eeed192d8576b9c4.png', '6v7be19pwman2fird04gqu53', 0); + +-- ---------------------------- +-- Table structure for pear_source_link +-- ---------------------------- +DROP TABLE IF EXISTS `pear_source_link`; +CREATE TABLE `pear_source_link` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `source_type` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源类型', + `source_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源编号', + `link_type` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联类型', + `link_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联编号', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织编码', + `create_by` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人', + `create_time` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '资源关联表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_source_link +-- ---------------------------- +INSERT INTO `pear_source_link` VALUES (4, '47eu1kg32wrdb9inq8zj5xas', 'file', 'lhp9dfz831jquoam6g4nbery', 'task', 'g15scwqm9zxroy7p8bvjt632', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0); +INSERT INTO `pear_source_link` VALUES (5, 'bkh7y02dinz9g3wuet1asr6x', 'file', 'lr08qzj5bucy2p1osinhkdef', 'task', 'g15scwqm9zxroy7p8bvjt632', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0); + +-- ---------------------------- +-- Table structure for pear_system_config +-- ---------------------------- +DROP TABLE IF EXISTS `pear_system_config`; +CREATE TABLE `pear_system_config` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '配置编码', + `value` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '配置值', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_system_config_name`(`name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统参数配置' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_system_config +-- ---------------------------- +INSERT INTO `pear_system_config` VALUES (1, 'app_name', 'Pear Project'); +INSERT INTO `pear_system_config` VALUES (2, 'site_name', 'Pear Project'); +INSERT INTO `pear_system_config` VALUES (3, 'app_version', '2.0'); +INSERT INTO `pear_system_config` VALUES (4, 'site_copy', 'Copyright © 2018 Pear Project出品'); +INSERT INTO `pear_system_config` VALUES (5, 'browser_icon', ''); +INSERT INTO `pear_system_config` VALUES (6, 'tongji_baidu_key', ''); +INSERT INTO `pear_system_config` VALUES (7, 'miitbeian', '粤ICP备16eeeee2号-2'); +INSERT INTO `pear_system_config` VALUES (8, 'storage_type', 'local'); +INSERT INTO `pear_system_config` VALUES (9, 'storage_local_exts', 'png,jpg,rar,doc,icon,mp4,zip,gif,jpeg,bmp,webp,mp4,m3u8,rmvb,avi,swf,3gp,mkv,flv,txt,docx,pages,epub,pdf,numbers,csv,xls,xlsx,keynote,ppt,pptx,mp3,wav,wma,ogg,aac,flac'); +INSERT INTO `pear_system_config` VALUES (10, 'storage_qiniu_bucket', ''); +INSERT INTO `pear_system_config` VALUES (11, 'storage_qiniu_domain', ''); +INSERT INTO `pear_system_config` VALUES (12, 'storage_qiniu_access_key', ''); +INSERT INTO `pear_system_config` VALUES (13, 'storage_qiniu_secret_key', ''); +INSERT INTO `pear_system_config` VALUES (14, 'storage_oss_bucket', 'cuci'); +INSERT INTO `pear_system_config` VALUES (15, 'storage_oss_endpoint', ''); +INSERT INTO `pear_system_config` VALUES (16, 'storage_oss_domain', ''); +INSERT INTO `pear_system_config` VALUES (17, 'storage_oss_keyid', ''); +INSERT INTO `pear_system_config` VALUES (18, 'storage_oss_secret', ''); +INSERT INTO `pear_system_config` VALUES (34, 'wechat_appid', ''); +INSERT INTO `pear_system_config` VALUES (35, 'wechat_appkey', ''); +INSERT INTO `pear_system_config` VALUES (36, 'storage_oss_is_https', 'http'); +INSERT INTO `pear_system_config` VALUES (37, 'wechat_type', 'thr'); +INSERT INTO `pear_system_config` VALUES (38, 'wechat_token', 'test'); +INSERT INTO `pear_system_config` VALUES (39, 'wechat_appsecret', ''); +INSERT INTO `pear_system_config` VALUES (40, 'wechat_encodingaeskey', ''); +INSERT INTO `pear_system_config` VALUES (41, 'wechat_thr_appid', ''); +INSERT INTO `pear_system_config` VALUES (42, 'wechat_thr_appkey', ''); + +-- ---------------------------- +-- Table structure for pear_system_log +-- ---------------------------- +DROP TABLE IF EXISTS `pear_system_log`; +CREATE TABLE `pear_system_log` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `ip` char(15) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '操作者IP地址', + `node` char(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '当前操作节点', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '操作人用户名', + `action` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '操作行为', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作内容描述', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统操作日志表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for pear_task +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task`; +CREATE TABLE `pear_task` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编号', + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '项目编号', + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pri` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '紧急程度', + `execute_status` enum('wait','doing','done','pause','cancel','closed') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'wait' COMMENT '执行状态', + `description` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '详情', + `create_by` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建日期', + `assign_to` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '指派给谁', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '回收站', + `stage_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '任务列表', + `task_tag` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务标签', + `done` tinyint(2) NULL DEFAULT 0 COMMENT '是否完成', + `begin_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开始时间', + `end_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '截止时间', + `remind_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '提醒时间', + `pcode` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '父任务id', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `like` int(7) NULL DEFAULT 0 COMMENT '点赞数', + `star` int(7) NULL DEFAULT 0 COMMENT '收藏数', + `deleted_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除时间', + `private` tinyint(1) NULL DEFAULT 0 COMMENT '是否隐私模式', + `id_num` int(7) NULL DEFAULT 1 COMMENT '任务id编号', + PRIMARY KEY (`id`, `project_code`) USING BTREE, + INDEX `task`(`code`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 12356 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_task +-- ---------------------------- +INSERT INTO `pear_task` VALUES (12182, 'mv4usefb06dx33ez2spkl223', 'mo4uqwfb06dxv8ez2spkl3rg', '排序样式', 0, 'wait', '', 'kqdcn2w40p58r31zyo6efjib', '2018-07-14 13:58:36', '', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '12173', 12182, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12183, 'mv4usefb06dxv8ez2spkl223', 'mo4uqwfb06dxv8ez2spkl3rg', 'Notification 组件升级 rc-notification 到 3.3.0', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2018-11-02 13:51:48', 'kqdcn2w40p58r31zyo6efjib', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, '2019-01-08 18:00', NULL, '', 1, 3, 1, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12208, 'aut9wrz1pn0elf5s47ivx26o', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Badge 代码错误引起的 TypeScript 类型报错', 2, 'wait', '

这里是备注内容


', '6v7be19pwman2fird04gqu53', '2018-12-25 16:13:34', 'y680trgedcavbhnz24u7i5m3', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 1, NULL, '', NULL, '', 0, 3, 1, NULL, 0, 3); +INSERT INTO `pear_task` VALUES (12218, '0tjma1un2gz8rf4ywo7c6de9', 'ibag9hw3o1tusd5qlpxrk782', '阅读「分享」中的使用案例,为新产品发布计划建立一个公示板吧!', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:17', '6v7be19pwman2fird04gqu53', 0, 'ipzyscgfo5l1qvah2xm4638t', NULL, 0, NULL, NULL, NULL, '', 12218, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12219, 'xkqg60sld15fcphwt4ya3rb8', 'ibag9hw3o1tusd5qlpxrk782', '编写文档', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:22', '6v7be19pwman2fird04gqu53', 0, 'ipzyscgfo5l1qvah2xm4638t', NULL, 1, NULL, NULL, NULL, '', 12219, 0, 0, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12223, '4mtnhwbe0gjdkaur2ic7xsv6', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 Table 动态设置表头分组报错的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:02:28', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12223, 0, 0, NULL, 0, 4); +INSERT INTO `pear_task` VALUES (12224, 'hj5s73zk6amd9wfvbxoygpic', 'mo4uqwfb06dxv8ez2spkl3rg', '新增阿拉伯语', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:12:06', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12224, 0, 0, NULL, 0, 5); +INSERT INTO `pear_task` VALUES (12225, 'l027b1dyrv93zu4ewmtoa6q5', 'mo4uqwfb06dxv8ez2spkl3rg', 'Table 支持 slot-scope 用法', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:14:09', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12225, 0, 0, NULL, 0, 6); +INSERT INTO `pear_task` VALUES (12226, 'rqb7vi254tna3uzhdgo0f6ey', 'mo4uqwfb06dxv8ez2spkl3rg', 'Table 新增取消全选事件 @on-select-all-cancel', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:15:16', '', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12226, 0, 0, NULL, 0, 7); +INSERT INTO `pear_task` VALUES (12287, 'qscug70y98zpk6edbnf3livr', 'ibag9hw3o1tusd5qlpxrk782', '1', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:43', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, '2018-12-31 18:00', NULL, '', 0, 1, 0, NULL, 0, 3); +INSERT INTO `pear_task` VALUES (12288, 'rzpu5cxl63fvb2y8gwdnsjqk', 'ibag9hw3o1tusd5qlpxrk782', '2', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:44', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 4); +INSERT INTO `pear_task` VALUES (12289, 'ozi8awms1lpcbde4fuq5ktgj', 'ibag9hw3o1tusd5qlpxrk782', '3', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:45', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 5); +INSERT INTO `pear_task` VALUES (12290, 'xejt6431q8ly97bkid5z2pun', 'ibag9hw3o1tusd5qlpxrk782', '4', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:46', 'y680trgedcavbhnz24u7i5m3', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 6); +INSERT INTO `pear_task` VALUES (12291, 'zkqb6if5ogdts27lx13r4yju', 'ibag9hw3o1tusd5qlpxrk782', '5', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:47', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 7); +INSERT INTO `pear_task` VALUES (12292, '9wsohy8jgapl6x2iutbm7k34', 'ibag9hw3o1tusd5qlpxrk782', '6', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:49', '', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 8); +INSERT INTO `pear_task` VALUES (12295, 'q9y6ksvtifwpuhna0e32jgm1', '8ulzfth64cd0k1x5peivowm2', '1', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:04', 'kqdcn2w40p58r31zyo6efjib', 0, 'pfi2ltmjhxuda90ncsgb5vwo', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12296, 'wyklgmhpt5qr47x3zsf9nibj', '8ulzfth64cd0k1x5peivowm2', '2', 0, 'wait', '

66

', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:13', '6v7be19pwman2fird04gqu53', 0, 'ht0gfnevaq7kp3ldx16i82yj', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12297, 'm6cloqrbh7tf0wg1jsvp9nay', 'mo4uqwfb06dxv8ez2spkl3rg', '99', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 11:00:15', 'y680trgedcavbhnz24u7i5m3', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 1, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 0, 0, 0, NULL, 0, 8); +INSERT INTO `pear_task` VALUES (12298, 'p1aujdigrlxky76h8cs3z4w0', 'mo4uqwfb06dxv8ez2spkl3rg', '增加了一个新组件 Comment', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:29', 'y680trgedcavbhnz24u7i5m3', 0, 'jvyswuxz34qk2cpt9o7ldb60', NULL, 0, NULL, '2019-01-01 18:00', NULL, '', 0, 0, 0, NULL, 0, 9); +INSERT INTO `pear_task` VALUES (12299, '2bn918l6ejyzousa73dkpgci', 'mo4uqwfb06dxv8ez2spkl3rg', '增加了一个新组件 ConfigProvider 为组件提供统一的全局化配置', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:37', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 10); +INSERT INTO `pear_task` VALUES (12300, '3qz5hfsin69xt8cgbd70lkew', 'mo4uqwfb06dxv8ez2spkl3rg', 'Avatar 组件增加 srcSet 属性,用于设置图片类头像响应式资源地址', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:45', 'kqdcn2w40p58r31zyo6efjib', 0, 'jvyswuxz34qk2cpt9o7ldb60', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 11); +INSERT INTO `pear_task` VALUES (12301, 'xkic58d20srnu9jm7ohqw14f', 'mo4uqwfb06dxv8ez2spkl3rg', '增加 less 变量 @font-variant-base 定制 font-variant 样式', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:53', 'kqdcn2w40p58r31zyo6efjib', 0, 'jvyswuxz34qk2cpt9o7ldb60', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 12); +INSERT INTO `pear_task` VALUES (12302, '6hj43ueim2bk187sqzcoy59v', 'mo4uqwfb06dxv8ez2spkl3rg', '优化鼠标悬停在可排序的表头上时 title 的显示', 2, 'wait', '

这里是备注内容


', '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:01', 'y680trgedcavbhnz24u7i5m3', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, '2019-01-06 18:00', NULL, '', 1, 1, 0, NULL, 0, 13); +INSERT INTO `pear_task` VALUES (12303, 'twb8f52jasn9vry6iko0dqg4', 'mo4uqwfb06dxv8ez2spkl3rg', '修正 Comment author 属性的类型为 ReactNode', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:09', '', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 0, 0, 1, NULL, 0, 14); +INSERT INTO `pear_task` VALUES (12304, 'gjmotpbrwva079ukde4izn38', 'mo4uqwfb06dxv8ez2spkl3rg', '优化 Spin 样式并略微提升了切换状态的性能', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:16', '6v7be19pwman2fird04gqu53', 0, 'p56enm7zck4id2rb0tx9lguh', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 15); +INSERT INTO `pear_task` VALUES (12305, 'uwq87z2f0hnvrl6o9gtcb3iy', 'mo4uqwfb06dxv8ez2spkl3rg', '微调 Card 头部和加载中的样式细节', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:21', 'y680trgedcavbhnz24u7i5m3', 0, 'p56enm7zck4id2rb0tx9lguh', NULL, 0, NULL, NULL, NULL, '', 0, 0, 2, NULL, 0, 16); +INSERT INTO `pear_task` VALUES (12306, 'qug5e4alndm7930ipxwyvc2h', 'mo4uqwfb06dxv8ez2spkl3rg', 'Cascader 升级 rc-calendar', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:04', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, '', 3, 0, 0, NULL, 0, 17); +INSERT INTO `pear_task` VALUES (12307, 'yctbsv81x6dmahkf7ei5o4r9', 'mo4uqwfb06dxv8ez2spkl3rg', 'Upload 组件升级 rc-upload 到 2.5.0', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:17', 'kqdcn2w40p58r31zyo6efjib', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, '', 4, 0, 0, NULL, 0, 18); +INSERT INTO `pear_task` VALUES (12308, 'm7u8fdp41cwrtkjxyzq2ion3', 'mo4uqwfb06dxv8ez2spkl3rg', '重构 Tag 组件,简化代码并提升性能', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:30', 'kqdcn2w40p58r31zyo6efjib', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 19); +INSERT INTO `pear_task` VALUES (12309, 'jo0i8fq2579kbdgsmcw1nev4', 'mo4uqwfb06dxv8ez2spkl3rg', 'Badge 进行了重构,count 支持自定义组件', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:36', '6v7be19pwman2fird04gqu53', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 3, 0, 0, NULL, 0, 20); +INSERT INTO `pear_task` VALUES (12310, 'owrs04m3e2klj8uqac6tiy17', 'mo4uqwfb06dxv8ez2spkl3rg', '重构了 Tree 底层的代码,以解决一些存在了很久的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:54', 'y680trgedcavbhnz24u7i5m3', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, '2019-01-12 22:27:56', 0, 21); +INSERT INTO `pear_task` VALUES (12311, 'g15scwqm9zxroy7p8bvjt632', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Divider 与浮动元素一起使用时的样式问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:21', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 22); +INSERT INTO `pear_task` VALUES (12312, '0a84xkg12enqjml7rz6dbifw', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Form 高级搜索模式下的样式问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:25', 'kqdcn2w40p58r31zyo6efjib', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, '2019-01-04 18:00', NULL, '', 0, 0, 0, NULL, 0, 23); +INSERT INTO `pear_task` VALUES (12313, 'fax4gez2jlk15tvsu3dc6p98', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Upload 对无扩展名图片地址的预览展示问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:29', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 24); +INSERT INTO `pear_task` VALUES (12314, 'zv4hx1ugpn98be5skc3wym72', 'mo4uqwfb06dxv8ez2spkl3rg', '修复一处 less 语法错误', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:33', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 25); +INSERT INTO `pear_task` VALUES (12315, 'jiy25eobh1cnp7ruvg9d0m6s', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 LocaleProvider 中 moment.locale 调用报错的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:39', '', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 26); +INSERT INTO `pear_task` VALUES (12316, '4pv9brqnm0cigwu5f3zeyxdk', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 WeekPicker 的 style 属性不生效的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:43', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 27); +INSERT INTO `pear_task` VALUES (12317, 'td1qznl9ms65gbcfej0k4vup', 'mo4uqwfb06dxv8ez2spkl3rg', 'Carousel: 升级 react-slick 版本以修复宽度计算错误', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:50', 'y680trgedcavbhnz24u7i5m3', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 28); +INSERT INTO `pear_task` VALUES (12318, 'fkrsvpzmj8xyo045hiugqt92', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 enterButton 的值为 button 元素时显示错误的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:56', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 29); +INSERT INTO `pear_task` VALUES (12319, '0b6wlc3754fr8gdvupx9aoys', 'mo4uqwfb06dxv8ez2spkl3rg', '修复表单校验文字消失的时候输入框会抖一下的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:02', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 30); +INSERT INTO `pear_task` VALUES (12320, 'bl1t7xjwpi9m2aocnsz83fk6', 'mo4uqwfb06dxv8ez2spkl3rg', '重构了 DatePicker 相关 type 定义', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:18', 'y680trgedcavbhnz24u7i5m3', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 31); +INSERT INTO `pear_task` VALUES (12321, 'hxntygarp3094c7w1856iujm', 'mo4uqwfb06dxv8ez2spkl3rg', 'Steps 进行了重构,首次渲染的时候不会再闪烁', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:24', '6v7be19pwman2fird04gqu53', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 32); +INSERT INTO `pear_task` VALUES (12322, 'gk8ipqm5406br7cwd9l1zefs', 'mo4uqwfb06dxv8ez2spkl3rg', 'Change hover over message of Column', 0, 'wait', NULL, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:09:28', 'y680trgedcavbhnz24u7i5m3', 0, 'p56enm7zck4id2rb0tx9lguh', NULL, 0, NULL, NULL, NULL, '6hj43ueim2bk187sqzcoy59v', 0, 0, 0, NULL, 0, 33); +INSERT INTO `pear_task` VALUES (12323, 'o61b3s24exmcy8njkparwthd', 'mo4uqwfb06dxv8ez2spkl3rg', '新增 directory 属性,支持上传一个文件夹', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:18', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'yctbsv81x6dmahkf7ei5o4r9', 0, 0, 0, NULL, 0, 34); +INSERT INTO `pear_task` VALUES (12324, 'orycwlhf7n2qx1pta038dzjk', 'mo4uqwfb06dxv8ez2spkl3rg', 'action 属性支持作为一个返回 Promise 对象的函数,使用更加灵活', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:24', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'yctbsv81x6dmahkf7ei5o4r9', 0, 0, 0, NULL, 0, 35); +INSERT INTO `pear_task` VALUES (12325, 'up6hn9bd34c8mglwaj1ytefz', 'elqa703jyvfhpt1dsxkzi8on', 'Add variant prop and deprecate fullWidth and scrollable props', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:13', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12326, 'krj4p7ix2cf605vyltmudq1e', 'elqa703jyvfhpt1dsxkzi8on', 'Add styles to make size property work with extended property', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:19', '', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, '2019-01-05 18:00', NULL, '', 0, 0, 0, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12327, '1g3vc8tkyla20fp5rdhxe7mo', 'elqa703jyvfhpt1dsxkzi8on', 'Add cross references from Modal docs to other components', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:46', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 3); +INSERT INTO `pear_task` VALUES (12328, 'nqrleu2c90zsdaj1yph4m8bt', 'elqa703jyvfhpt1dsxkzi8on', 'Add createSvgIcon type definition', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:25', 'kqdcn2w40p58r31zyo6efjib', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 4); +INSERT INTO `pear_task` VALUES (12329, 'mix3cg2eh1u60fknd7yz9v5t', 'elqa703jyvfhpt1dsxkzi8on', 'Add customized demo', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:37', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 5); +INSERT INTO `pear_task` VALUES (12330, 'dckxz1vpujtafshgr20mwo7e', 'elqa703jyvfhpt1dsxkzi8on', 'Add defaultTheme option for makeStyles', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:45', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 6); +INSERT INTO `pear_task` VALUES (12331, 'fd1avskez2q43w80xhb7ypc9', 'elqa703jyvfhpt1dsxkzi8on', 'Add nextjs-hooks-with-typescript', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:53', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 7); +INSERT INTO `pear_task` VALUES (12332, 'as2y4r6mwxuhgvncop3f8z90', 'elqa703jyvfhpt1dsxkzi8on', 'Add note on archived components', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:01', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 8); +INSERT INTO `pear_task` VALUES (12333, '8zj3vpx0b7qud24ylfgces1m', 'elqa703jyvfhpt1dsxkzi8on', 'Add Instagram theme', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:08', 'kqdcn2w40p58r31zyo6efjib', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 9); +INSERT INTO `pear_task` VALUES (12334, 'hcrdvbuzwgojst2f0p134qxi', 'elqa703jyvfhpt1dsxkzi8on', 'Add component prop', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:18', 'kqdcn2w40p58r31zyo6efjib', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 10); +INSERT INTO `pear_task` VALUES (12335, 'lmognshqz21dbewcu9a3rx87', 'elqa703jyvfhpt1dsxkzi8on', 'Fix utils.chainPropTypes issue', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:37', 'kqdcn2w40p58r31zyo6efjib', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 11); +INSERT INTO `pear_task` VALUES (12336, 'n6ulc7ebxpqahi50dy9k1sgf', 'elqa703jyvfhpt1dsxkzi8on', 'Fix vertical text alignment by reducing padding', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:51', '', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 12); +INSERT INTO `pear_task` VALUES (12337, 'rqjng1kfcp4wyiamt6o23zbu', 'elqa703jyvfhpt1dsxkzi8on', 'Fix infinite loop in the scroll button logic', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:57', 'y680trgedcavbhnz24u7i5m3', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 13); +INSERT INTO `pear_task` VALUES (12338, 'qsz65fvgi8hyx3e7bn14o9wm', 'elqa703jyvfhpt1dsxkzi8on', 'Fix component animations', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:05', 'kqdcn2w40p58r31zyo6efjib', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 14); +INSERT INTO `pear_task` VALUES (12339, 'byiuxhn0v6sod4zap1t2fclr', 'elqa703jyvfhpt1dsxkzi8on', 'Fix responsivePropType typo', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:12', 'kqdcn2w40p58r31zyo6efjib', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 15); +INSERT INTO `pear_task` VALUES (12340, 'jxd3rpmay6qonsk1i8wg5e9u', 'elqa703jyvfhpt1dsxkzi8on', 'Change action element to have a fixed right margin', 2, 'wait', '

This is the content


', '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:27', 'kqdcn2w40p58r31zyo6efjib', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, '2019-01-09 18:00', NULL, '', 0, 0, 0, NULL, 0, 16); +INSERT INTO `pear_task` VALUES (12341, 'vmzeciodgbfp7ysu38tq10kj', 'elqa703jyvfhpt1dsxkzi8on', 'Change height from 5 to 4 pixels', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:33', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 17); +INSERT INTO `pear_task` VALUES (12342, '6cagd725tifonvw0qphe9zsb', 'elqa703jyvfhpt1dsxkzi8on', 'Change sub-components to have fixed gutters', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:45', 'kqdcn2w40p58r31zyo6efjib', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 18); +INSERT INTO `pear_task` VALUES (12343, 'xu3jgyow2s9f1km0rctqin4v', 'elqa703jyvfhpt1dsxkzi8on', 'Change the classes structure to match the core components convention', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:54', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 19); +INSERT INTO `pear_task` VALUES (12344, 'k3g07m1qyctvbp95siohju6f', 'elqa703jyvfhpt1dsxkzi8on', 'Update the action spacing to better match the spec', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:12', 'y680trgedcavbhnz24u7i5m3', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 1, 3, 0, NULL, 0, 20); +INSERT INTO `pear_task` VALUES (12345, 'oh5wpj9kd8e6ltusxq271ma3', 'elqa703jyvfhpt1dsxkzi8on', 'Update the emotion documentation', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:18', 'y680trgedcavbhnz24u7i5m3', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 21); +INSERT INTO `pear_task` VALUES (12346, 'akdwslbtp3z82xecui0y4ovq', 'elqa703jyvfhpt1dsxkzi8on', 'Update the CodeFund embed script', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:25', 'y680trgedcavbhnz24u7i5m3', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 3, 0, 0, NULL, 0, 22); +INSERT INTO `pear_task` VALUES (12347, 'hayfr6vl398nq5exgszobu2j', 'elqa703jyvfhpt1dsxkzi8on', 'Update react-select demo to have isClearable set to true', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:31', '', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 4, 0, 0, NULL, 0, 23); +INSERT INTO `pear_task` VALUES (12348, 'mf80iu15kepavbg2r9ldcjsh', 'elqa703jyvfhpt1dsxkzi8on', 'Update album page-layout preview image album.png', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:41', 'kqdcn2w40p58r31zyo6efjib', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 5, 0, 0, NULL, 0, 24); +INSERT INTO `pear_task` VALUES (12349, 'nzy71f5i6g0skwau4lrj3d8b', 'elqa703jyvfhpt1dsxkzi8on', 'Update some components to better match the Material specification', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:08', 'kqdcn2w40p58r31zyo6efjib', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 25); +INSERT INTO `pear_task` VALUES (12350, '4cug3e5rodalq9x81ywht0zn', 'elqa703jyvfhpt1dsxkzi8on', 'Remove hoisting of static properties in HOCs', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:20', 'kqdcn2w40p58r31zyo6efjib', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, '2018-12-31 18:00', NULL, '', 0, 0, 0, NULL, 0, 26); +INSERT INTO `pear_task` VALUES (12351, '92fow0le47htb6xkv5ynzuri', 'elqa703jyvfhpt1dsxkzi8on', 'Remove the withRoot HOC', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:24', 'kqdcn2w40p58r31zyo6efjib', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 27); +INSERT INTO `pear_task` VALUES (12352, '6ky18i9cg0eqvfzn2th3ux5l', 'elqa703jyvfhpt1dsxkzi8on', '100% remove the prop types', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:34', '', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 28); +INSERT INTO `pear_task` VALUES (12353, 'zj6skt9orn748gh5mvb2ueif', 'elqa703jyvfhpt1dsxkzi8on', 'Remove unused lint directives', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:40', 'y680trgedcavbhnz24u7i5m3', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 29); +INSERT INTO `pear_task` VALUES (12354, 'a75dcqx2sjivokmg49yh380l', 'elqa703jyvfhpt1dsxkzi8on', 'Improve demos loading', 0, 'wait', NULL, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:13', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, 'jxd3rpmay6qonsk1i8wg5e9u', 0, 0, 0, NULL, 0, 30); +INSERT INTO `pear_task` VALUES (12355, '7ns924ofulpjxkgq06y3bm5r', 'elqa703jyvfhpt1dsxkzi8on', 'Improve the service-worker logic', 0, 'wait', NULL, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:19', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, 'jxd3rpmay6qonsk1i8wg5e9u', 0, 0, 0, NULL, 0, 31); + +-- ---------------------------- +-- Table structure for pear_task_like +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_like`; +CREATE TABLE `pear_task_like` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务ID', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 117 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务点赞表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_task_like +-- ---------------------------- +INSERT INTO `pear_task_like` VALUES (91, 'n156qrj4l8720xhvioefmzys', '6v7be19pwman2fird04gqu53', '2018-12-28 20:37:49'); +INSERT INTO `pear_task_like` VALUES (100, 'caq96fw7hnsv1pude2mibxz8', '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:32'); +INSERT INTO `pear_task_like` VALUES (101, 'j6xkdynh4c2sm1pblvztaweg', '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:35'); +INSERT INTO `pear_task_like` VALUES (102, '4fua38vpqgk706csx2lb9etj', '6v7be19pwman2fird04gqu53', '2018-12-29 13:07:20'); +INSERT INTO `pear_task_like` VALUES (105, 'aut9wrz1pn0elf5s47ivx26o', '6v7be19pwman2fird04gqu53', '2018-12-30 21:51:39'); +INSERT INTO `pear_task_like` VALUES (108, 'mv4usefb06dxv8ez2spkl223', '6v7be19pwman2fird04gqu53', '2018-12-31 14:08:27'); +INSERT INTO `pear_task_like` VALUES (109, 'qscug70y98zpk6edbnf3livr', '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:57'); +INSERT INTO `pear_task_like` VALUES (111, 'mv4usefb06dxv8ez2spkl223', 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:20:34'); +INSERT INTO `pear_task_like` VALUES (112, 'mv4usefb06dxv8ez2spkl223', 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:20:43'); +INSERT INTO `pear_task_like` VALUES (113, 'k3g07m1qyctvbp95siohju6f', 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:32:09'); +INSERT INTO `pear_task_like` VALUES (114, 'k3g07m1qyctvbp95siohju6f', 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:32:12'); +INSERT INTO `pear_task_like` VALUES (115, 'k3g07m1qyctvbp95siohju6f', '6v7be19pwman2fird04gqu53', '2019-01-04 21:32:23'); +INSERT INTO `pear_task_like` VALUES (116, '6hj43ueim2bk187sqzcoy59v', '6v7be19pwman2fird04gqu53', '2019-01-06 19:58:07'); + +-- ---------------------------- +-- Table structure for pear_task_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_member`; +CREATE TABLE `pear_task_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务ID', + `is_executor` tinyint(1) NULL DEFAULT 0 COMMENT '执行者', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `join_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_owner` tinyint(1) NULL DEFAULT 0 COMMENT '是否创建人', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 263 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务-成员表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_task_member +-- ---------------------------- +INSERT INTO `pear_task_member` VALUES (1, 'mv4usefb06dxv8ez2spkl223', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-04-30 22:33:22', 0); +INSERT INTO `pear_task_member` VALUES (4, 'c3s1n5avuqgeoh2xb4yt809l', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:39:34', 0); +INSERT INTO `pear_task_member` VALUES (5, 'tx6loaugrd0s3e1mhk52iznp', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:47:08', 0); +INSERT INTO `pear_task_member` VALUES (6, 'n156qrj4l8720xhvioefmzys', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:47:26', 0); +INSERT INTO `pear_task_member` VALUES (7, 'b7upmiofztckvy6s38lxge90', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:48:17', 0); +INSERT INTO `pear_task_member` VALUES (8, '9av8miueqc7wbzo50ljg3p1r', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:49:16', 0); +INSERT INTO `pear_task_member` VALUES (9, '9av8miueqc7wbzo50ljg3p1r', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:49:16', 0); +INSERT INTO `pear_task_member` VALUES (10, '7agxbmcn4y1rzviw26s0du8p', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:53:46', 0); +INSERT INTO `pear_task_member` VALUES (11, 'kd2vz0jeyhwcl1ir9m8f64ap', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:54:56', 0); +INSERT INTO `pear_task_member` VALUES (12, 'kd2vz0jeyhwcl1ir9m8f64ap', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 14:54:56', 0); +INSERT INTO `pear_task_member` VALUES (13, 'ycs3hzrpfmjq4o19a8k2x7ln', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:01:39', 0); +INSERT INTO `pear_task_member` VALUES (14, 'ycs3hzrpfmjq4o19a8k2x7ln', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:01:39', 0); +INSERT INTO `pear_task_member` VALUES (15, 'yqugz409cvs8p165fx2miodn', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:02:18', 0); +INSERT INTO `pear_task_member` VALUES (16, 'yd5blem7xikvq01ujhwfzc4n', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:02:47', 0); +INSERT INTO `pear_task_member` VALUES (17, '9u0j2xvr37og8n1bpk6dwfsz', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:03:06', 0); +INSERT INTO `pear_task_member` VALUES (18, '9u0j2xvr37og8n1bpk6dwfsz', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:03:06', 0); +INSERT INTO `pear_task_member` VALUES (19, 'caq96fw7hnsv1pude2mibxz8', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:03:09', 0); +INSERT INTO `pear_task_member` VALUES (20, 'caq96fw7hnsv1pude2mibxz8', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:03:09', 0); +INSERT INTO `pear_task_member` VALUES (21, 'ubxoy07emt4lfij2cn961spd', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:03:11', 0); +INSERT INTO `pear_task_member` VALUES (22, 'ubxoy07emt4lfij2cn961spd', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:03:11', 0); +INSERT INTO `pear_task_member` VALUES (23, '3kpstlv16ixmaho78fuqc05e', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:07:41', 0); +INSERT INTO `pear_task_member` VALUES (24, 'uwdo4eytilf2bcx1qn5pz8vg', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:08:06', 0); +INSERT INTO `pear_task_member` VALUES (25, '0ib1k5v2pn4mdjsxa3eo6f8y', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:08:19', 0); +INSERT INTO `pear_task_member` VALUES (26, 'ndu3t1r7i09x6al2egsopzyf', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:16:55', 0); +INSERT INTO `pear_task_member` VALUES (27, '2wkhps0gviqcmfr5y8t1nu6d', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:19:26', 0); +INSERT INTO `pear_task_member` VALUES (28, 'sgkw8x2nte10o97i3dyumv4a', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:02', 0); +INSERT INTO `pear_task_member` VALUES (29, 'lremxqhjw265i3ku4p0bo8n1', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:40', 0); +INSERT INTO `pear_task_member` VALUES (30, 'dfcolxj2izhk08pq7stmu15v', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:42', 0); +INSERT INTO `pear_task_member` VALUES (31, 'j6xkdynh4c2sm1pblvztaweg', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:50', 0); +INSERT INTO `pear_task_member` VALUES (32, '7y9o8jrfxus3ca1i4bwvptgh', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:51', 0); +INSERT INTO `pear_task_member` VALUES (33, '6zkho2cegrl8fxq9wia1jmsp', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:52', 0); +INSERT INTO `pear_task_member` VALUES (35, '7qcosftd9bl83i62ugymkxaz', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:33:14', 0); +INSERT INTO `pear_task_member` VALUES (36, '98lqy4vaiprk60uzbojdmsgx', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:33:23', 0); +INSERT INTO `pear_task_member` VALUES (37, 'i7s50ny8j9p2km3hfau6le4r', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:03', 0); +INSERT INTO `pear_task_member` VALUES (38, 'kr5vojwa2hd370mxpgs984zt', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:09', 0); +INSERT INTO `pear_task_member` VALUES (39, 'edbn6rz89fmh7a3ilgvukw14', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:11', 0); +INSERT INTO `pear_task_member` VALUES (40, 'fb3hsvx1e6tjad450y9cgr8o', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:15', 0); +INSERT INTO `pear_task_member` VALUES (41, 'f09otzl5e1r8qspgcvdy4ukx', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 20:09:17', 0); +INSERT INTO `pear_task_member` VALUES (42, 'r3efvs51qn9dy0c6mik2u7xw', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 21:48:48', 0); +INSERT INTO `pear_task_member` VALUES (43, 'zabrco15knhvj4xwm69yd7et', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 09:08:15', 0); +INSERT INTO `pear_task_member` VALUES (44, '0tjma1un2gz8rf4ywo7c6de9', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:17', 0); +INSERT INTO `pear_task_member` VALUES (45, 'xkqg60sld15fcphwt4ya3rb8', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:22', 0); +INSERT INTO `pear_task_member` VALUES (46, 'bodic0rp491g837vah5tsxqn', 0, '6v7be19pwman2fird04gqu53', '2018-12-26 11:30:32', 0); +INSERT INTO `pear_task_member` VALUES (47, '3urs09e57btygqhjdfx2pwmn', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 16:08:36', 0); +INSERT INTO `pear_task_member` VALUES (53, 'aut9wrz1pn0elf5s47ivx26o', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-27 14:45:06', 1); +INSERT INTO `pear_task_member` VALUES (65, 'sgkw8x2nte10o97i3dyumv4a', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-27 15:19:22', 0); +INSERT INTO `pear_task_member` VALUES (66, 'dfcolxj2izhk08pq7stmu15v', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-27 15:20:42', 0); +INSERT INTO `pear_task_member` VALUES (67, 'edbn6rz89fmh7a3ilgvukw14', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-27 15:21:15', 0); +INSERT INTO `pear_task_member` VALUES (68, 'l09jk1p7z4x3ebm2rvwsuthn', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:00:53', 1); +INSERT INTO `pear_task_member` VALUES (69, '4mtnhwbe0gjdkaur2ic7xsv6', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:02:28', 1); +INSERT INTO `pear_task_member` VALUES (70, 'hj5s73zk6amd9wfvbxoygpic', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:12:06', 1); +INSERT INTO `pear_task_member` VALUES (71, 'l027b1dyrv93zu4ewmtoa6q5', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:14:09', 1); +INSERT INTO `pear_task_member` VALUES (72, 'xu7cpf2h3t6o0rilam8k9sgj', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:19:13', 1); +INSERT INTO `pear_task_member` VALUES (73, '0zvn3ug6fiqhdpkljos79xaw', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:42:23', 1); +INSERT INTO `pear_task_member` VALUES (74, 'we7iyhrudos9lt50cn8fjzp2', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 13:45:08', 0); +INSERT INTO `pear_task_member` VALUES (75, 'we7iyhrudos9lt50cn8fjzp2', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:45:09', 1); +INSERT INTO `pear_task_member` VALUES (76, '6kqh4b1mce05rzvuljsg3ow8', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:45:19', 1); +INSERT INTO `pear_task_member` VALUES (77, 'g83vs5t47dfnprchqzel1a29', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 13:45:27', 0); +INSERT INTO `pear_task_member` VALUES (78, 'g83vs5t47dfnprchqzel1a29', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:45:27', 1); +INSERT INTO `pear_task_member` VALUES (79, 'mevzwp1d086roxsb92n4yja7', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 14:36:29', 1); +INSERT INTO `pear_task_member` VALUES (80, '5qd1lfth0gji4aeb3oczp9u8', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 14:36:32', 1); +INSERT INTO `pear_task_member` VALUES (81, 'bodic0rp491g837vah5tsxqn', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:02:24', 0); +INSERT INTO `pear_task_member` VALUES (82, 'ua7rphit5fxj04l6qc8nydze', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:08:19', 1); +INSERT INTO `pear_task_member` VALUES (83, 'ua7rphit5fxj04l6qc8nydze', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:09:53', 0); +INSERT INTO `pear_task_member` VALUES (84, 'yis4dg3txpoah6jnr290v57w', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:56:32', 0); +INSERT INTO `pear_task_member` VALUES (85, 'yis4dg3txpoah6jnr290v57w', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:56:32', 1); +INSERT INTO `pear_task_member` VALUES (86, 'bzn09o6a3j1qe4sp7il8tvxy', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:56:35', 0); +INSERT INTO `pear_task_member` VALUES (87, 'bzn09o6a3j1qe4sp7il8tvxy', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:56:35', 1); +INSERT INTO `pear_task_member` VALUES (88, '3gkj1lmzxpcf5e628thnwsb7', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:56:40', 0); +INSERT INTO `pear_task_member` VALUES (89, '3gkj1lmzxpcf5e628thnwsb7', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:56:40', 1); +INSERT INTO `pear_task_member` VALUES (90, 'fr091dxea76hvzgp42ywctmj', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:18:57', 0); +INSERT INTO `pear_task_member` VALUES (91, 'fr091dxea76hvzgp42ywctmj', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:18:58', 1); +INSERT INTO `pear_task_member` VALUES (92, 'n6txie0u7mz8cghyw1kjaof5', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:18:59', 0); +INSERT INTO `pear_task_member` VALUES (93, 'n6txie0u7mz8cghyw1kjaof5', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:18:59', 1); +INSERT INTO `pear_task_member` VALUES (94, '8uhmp6ekvwl5zny7gr3bo9a1', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:19:00', 0); +INSERT INTO `pear_task_member` VALUES (95, '8uhmp6ekvwl5zny7gr3bo9a1', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:00', 1); +INSERT INTO `pear_task_member` VALUES (96, 'ibc0m5jly7k4wngfper2dstx', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:19:01', 0); +INSERT INTO `pear_task_member` VALUES (97, 'ibc0m5jly7k4wngfper2dstx', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:01', 1); +INSERT INTO `pear_task_member` VALUES (98, 'lcxa0u8srgkq46fy7pw2b3o5', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 23:19:06', 0); +INSERT INTO `pear_task_member` VALUES (99, 'lcxa0u8srgkq46fy7pw2b3o5', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:06', 1); +INSERT INTO `pear_task_member` VALUES (100, 'sora9u5ti4zlxhy3men12vb8', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:18', 1); +INSERT INTO `pear_task_member` VALUES (101, 'rat4seg68kpi0yqv793fux1l', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:20', 1); +INSERT INTO `pear_task_member` VALUES (102, 'aryd8zjbh3f20oln4gekv57x', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:30', 1); +INSERT INTO `pear_task_member` VALUES (106, 'oz2xwp8v0niahdc7lekjtsrg', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 11:02:39', 1); +INSERT INTO `pear_task_member` VALUES (107, 'v2kr731dihezctslmx9agb5w', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-29 11:02:49', 0); +INSERT INTO `pear_task_member` VALUES (108, 'v2kr731dihezctslmx9agb5w', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 11:02:49', 1); +INSERT INTO `pear_task_member` VALUES (109, '1vqt5zg4shbkum2dc8jxow7f', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:10:16', 0); +INSERT INTO `pear_task_member` VALUES (110, '1vqt5zg4shbkum2dc8jxow7f', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:10:16', 1); +INSERT INTO `pear_task_member` VALUES (111, '2hg0yfa9jpxz6q58ckrdol1u', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:11:40', 0); +INSERT INTO `pear_task_member` VALUES (112, '2hg0yfa9jpxz6q58ckrdol1u', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:11:40', 1); +INSERT INTO `pear_task_member` VALUES (113, 'bi5ajpmxfsk9rwdg4l32yv1n', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:11:57', 0); +INSERT INTO `pear_task_member` VALUES (114, 'bi5ajpmxfsk9rwdg4l32yv1n', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:11:57', 1); +INSERT INTO `pear_task_member` VALUES (115, 'dmbtgy3phi2sz7j89q10xcoa', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:12:32', 0); +INSERT INTO `pear_task_member` VALUES (116, 'dmbtgy3phi2sz7j89q10xcoa', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:32', 1); +INSERT INTO `pear_task_member` VALUES (117, 'ywiahqcb50rkem376js9nflv', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:41', 1); +INSERT INTO `pear_task_member` VALUES (118, 'xebf08p2uc9sj6mkr5714lat', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:42', 1); +INSERT INTO `pear_task_member` VALUES (119, 'p5axvq94lr7enmhb83o2zfks', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:44', 1); +INSERT INTO `pear_task_member` VALUES (120, 'lert5uyi790q2jfpbmzgak1o', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:14:21', 1); +INSERT INTO `pear_task_member` VALUES (121, '25rzhoykix6pajsw9cgm1408', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:14:30', 1); +INSERT INTO `pear_task_member` VALUES (122, 'cmrdn8soz4g26fy0qa3ib7te', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:15:33', 1); +INSERT INTO `pear_task_member` VALUES (123, 'qfakryig3ztpv5uw6e2obx08', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:16:19', 1); +INSERT INTO `pear_task_member` VALUES (124, 'e16tvkg4uoixnz9hjaf5l3wr', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:17', 1); +INSERT INTO `pear_task_member` VALUES (125, 'f35oyrln286s7p4u9vmeghdw', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:18', 1); +INSERT INTO `pear_task_member` VALUES (126, 'yqwe2aiupvg5zj1so4krm86l', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:19', 1); +INSERT INTO `pear_task_member` VALUES (127, 'yhs4xb7g3vu9pnwfq5l1zioa', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:22', 1); +INSERT INTO `pear_task_member` VALUES (128, 'jo3vbswk8ze57filmcptun2g', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:52', 1); +INSERT INTO `pear_task_member` VALUES (129, '35i8ptd1fs9hrbk0glv47ycj', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:53', 1); +INSERT INTO `pear_task_member` VALUES (130, 'pn3dmjavhrc1bewgi4yz25x9', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:54', 1); +INSERT INTO `pear_task_member` VALUES (131, 'p5r8a4gwfxmn9sqit2jvkl71', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:55', 1); +INSERT INTO `pear_task_member` VALUES (132, 'aenxk82rgwm5qcsuo0b7hi4f', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:23:00', 1); +INSERT INTO `pear_task_member` VALUES (133, 'vf8xtpclba3od21kmih5us9q', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:24:25', 1); +INSERT INTO `pear_task_member` VALUES (134, '2ohuv1jm3abrs8ldni5p9z06', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:24:26', 1); +INSERT INTO `pear_task_member` VALUES (135, 'wamzl34n6jfrhiyoe09v2ktb', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:39', 1); +INSERT INTO `pear_task_member` VALUES (136, 'rgshi61c4xol5ue9f2n8m3bd', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:40', 1); +INSERT INTO `pear_task_member` VALUES (137, 'kny3xf7o41rli9s0pwjmhdqc', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:41', 1); +INSERT INTO `pear_task_member` VALUES (138, 'y2zos3hg9058alwet46xmukp', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:42', 1); +INSERT INTO `pear_task_member` VALUES (139, 'wqu7verc5i4p19h0y32x6tmo', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:43', 1); +INSERT INTO `pear_task_member` VALUES (140, '0h8mt7sw1ki5fuxv2y6d3jag', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:44', 1); +INSERT INTO `pear_task_member` VALUES (141, '1hcrmvgfjtu5qnadl869bkxo', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:46', 1); +INSERT INTO `pear_task_member` VALUES (142, 'puj84l5av3f0e7k9oyw2zqcr', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:54', 1); +INSERT INTO `pear_task_member` VALUES (143, 'jftz4y5is1hwrlmac07nd8q6', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:56', 1); +INSERT INTO `pear_task_member` VALUES (144, '4fua38vpqgk706csx2lb9etj', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 13:05:03', 1); +INSERT INTO `pear_task_member` VALUES (146, 's5bxrym2p8qtjch1i6gnkvaw', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 16:08:33', 1); +INSERT INTO `pear_task_member` VALUES (147, 'aut9wrz1pn0elf5s47ivx26o', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 21:47:17', 0); +INSERT INTO `pear_task_member` VALUES (148, 'w80m92aopfbcru6s5qe7z3ti', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 10:24:40', 1); +INSERT INTO `pear_task_member` VALUES (149, 'wrjgk84t2beam0yvxs61qinu', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 10:24:42', 1); +INSERT INTO `pear_task_member` VALUES (150, 'n9pe164krv8zghofilw750jq', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 15:38:03', 1); +INSERT INTO `pear_task_member` VALUES (151, 'oq7d3wbklenf12pgvuxhimr8', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 15:39:15', 1); +INSERT INTO `pear_task_member` VALUES (153, 'mv4usefb06dxv8ez2spkl223', 0, '6v7be19pwman2fird04gqu53', '2018-12-31 10:49:34', 0); +INSERT INTO `pear_task_member` VALUES (154, 'mv4usefb06dxv8ez2spkl223', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-31 10:49:34', 0); +INSERT INTO `pear_task_member` VALUES (155, 'qscug70y98zpk6edbnf3livr', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:43', 1); +INSERT INTO `pear_task_member` VALUES (156, 'rzpu5cxl63fvb2y8gwdnsjqk', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:44', 1); +INSERT INTO `pear_task_member` VALUES (157, 'ozi8awms1lpcbde4fuq5ktgj', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:45', 1); +INSERT INTO `pear_task_member` VALUES (158, 'xejt6431q8ly97bkid5z2pun', 0, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:46', 1); +INSERT INTO `pear_task_member` VALUES (159, 'zkqb6if5ogdts27lx13r4yju', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:47', 1); +INSERT INTO `pear_task_member` VALUES (160, '9wsohy8jgapl6x2iutbm7k34', 0, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:49', 1); +INSERT INTO `pear_task_member` VALUES (161, 'xejt6431q8ly97bkid5z2pun', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-31 15:23:08', 0); +INSERT INTO `pear_task_member` VALUES (162, 'q9y6ksvtifwpuhna0e32jgm1', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:04', 1); +INSERT INTO `pear_task_member` VALUES (163, 'wyklgmhpt5qr47x3zsf9nibj', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:14', 1); +INSERT INTO `pear_task_member` VALUES (164, 'wyklgmhpt5qr47x3zsf9nibj', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 10:51:44', 0); +INSERT INTO `pear_task_member` VALUES (165, 'm6cloqrbh7tf0wg1jsvp9nay', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 11:00:15', 0); +INSERT INTO `pear_task_member` VALUES (166, 'm6cloqrbh7tf0wg1jsvp9nay', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 11:00:15', 1); +INSERT INTO `pear_task_member` VALUES (167, 'p1aujdigrlxky76h8cs3z4w0', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:30', 1); +INSERT INTO `pear_task_member` VALUES (168, '2bn918l6ejyzousa73dkpgci', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:37', 1); +INSERT INTO `pear_task_member` VALUES (169, '3qz5hfsin69xt8cgbd70lkew', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:45', 1); +INSERT INTO `pear_task_member` VALUES (170, 'xkic58d20srnu9jm7ohqw14f', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:53', 1); +INSERT INTO `pear_task_member` VALUES (171, '6hj43ueim2bk187sqzcoy59v', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:01', 1); +INSERT INTO `pear_task_member` VALUES (172, 'twb8f52jasn9vry6iko0dqg4', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:09', 1); +INSERT INTO `pear_task_member` VALUES (173, 'gjmotpbrwva079ukde4izn38', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:16', 1); +INSERT INTO `pear_task_member` VALUES (174, 'uwq87z2f0hnvrl6o9gtcb3iy', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:21', 1); +INSERT INTO `pear_task_member` VALUES (175, 'qug5e4alndm7930ipxwyvc2h', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:04', 1); +INSERT INTO `pear_task_member` VALUES (176, 'yctbsv81x6dmahkf7ei5o4r9', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:17', 1); +INSERT INTO `pear_task_member` VALUES (177, 'm7u8fdp41cwrtkjxyzq2ion3', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:30', 1); +INSERT INTO `pear_task_member` VALUES (178, 'jo0i8fq2579kbdgsmcw1nev4', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:36', 1); +INSERT INTO `pear_task_member` VALUES (179, 'owrs04m3e2klj8uqac6tiy17', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:54', 1); +INSERT INTO `pear_task_member` VALUES (180, 'g15scwqm9zxroy7p8bvjt632', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:21', 1); +INSERT INTO `pear_task_member` VALUES (181, '0a84xkg12enqjml7rz6dbifw', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:25', 1); +INSERT INTO `pear_task_member` VALUES (182, 'fax4gez2jlk15tvsu3dc6p98', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:29', 1); +INSERT INTO `pear_task_member` VALUES (183, 'zv4hx1ugpn98be5skc3wym72', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:33', 1); +INSERT INTO `pear_task_member` VALUES (184, 'jiy25eobh1cnp7ruvg9d0m6s', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:39', 1); +INSERT INTO `pear_task_member` VALUES (185, '4pv9brqnm0cigwu5f3zeyxdk', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:43', 1); +INSERT INTO `pear_task_member` VALUES (186, 'td1qznl9ms65gbcfej0k4vup', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:50', 1); +INSERT INTO `pear_task_member` VALUES (187, 'fkrsvpzmj8xyo045hiugqt92', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:56', 1); +INSERT INTO `pear_task_member` VALUES (188, '0b6wlc3754fr8gdvupx9aoys', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:02', 1); +INSERT INTO `pear_task_member` VALUES (189, 'bl1t7xjwpi9m2aocnsz83fk6', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:18', 1); +INSERT INTO `pear_task_member` VALUES (190, 'hxntygarp3094c7w1856iujm', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:24', 1); +INSERT INTO `pear_task_member` VALUES (191, 'bl1t7xjwpi9m2aocnsz83fk6', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:29:42', 0); +INSERT INTO `pear_task_member` VALUES (192, '0a84xkg12enqjml7rz6dbifw', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:30:00', 0); +INSERT INTO `pear_task_member` VALUES (193, 'td1qznl9ms65gbcfej0k4vup', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:30:15', 0); +INSERT INTO `pear_task_member` VALUES (194, 'uwq87z2f0hnvrl6o9gtcb3iy', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:30:27', 0); +INSERT INTO `pear_task_member` VALUES (195, '3qz5hfsin69xt8cgbd70lkew', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:30:31', 0); +INSERT INTO `pear_task_member` VALUES (196, 'xkic58d20srnu9jm7ohqw14f', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:30:38', 0); +INSERT INTO `pear_task_member` VALUES (197, 'owrs04m3e2klj8uqac6tiy17', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:30:46', 0); +INSERT INTO `pear_task_member` VALUES (198, '6hj43ueim2bk187sqzcoy59v', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 08:57:04', 0); +INSERT INTO `pear_task_member` VALUES (199, 'gk8ipqm5406br7cwd9l1zefs', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:09:29', 1); +INSERT INTO `pear_task_member` VALUES (200, 'm7u8fdp41cwrtkjxyzq2ion3', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:17:51', 0); +INSERT INTO `pear_task_member` VALUES (201, 'o61b3s24exmcy8njkparwthd', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 09:18:18', 0); +INSERT INTO `pear_task_member` VALUES (202, 'o61b3s24exmcy8njkparwthd', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:18', 1); +INSERT INTO `pear_task_member` VALUES (203, 'orycwlhf7n2qx1pta038dzjk', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 09:18:24', 0); +INSERT INTO `pear_task_member` VALUES (204, 'orycwlhf7n2qx1pta038dzjk', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:24', 1); +INSERT INTO `pear_task_member` VALUES (205, 'yctbsv81x6dmahkf7ei5o4r9', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:50', 0); +INSERT INTO `pear_task_member` VALUES (206, 'p1aujdigrlxky76h8cs3z4w0', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:19:58', 0); +INSERT INTO `pear_task_member` VALUES (207, 'up6hn9bd34c8mglwaj1ytefz', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:13', 1); +INSERT INTO `pear_task_member` VALUES (208, 'krj4p7ix2cf605vyltmudq1e', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:19', 1); +INSERT INTO `pear_task_member` VALUES (209, '1g3vc8tkyla20fp5rdhxe7mo', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:17:46', 0); +INSERT INTO `pear_task_member` VALUES (210, '1g3vc8tkyla20fp5rdhxe7mo', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:46', 1); +INSERT INTO `pear_task_member` VALUES (211, 'nqrleu2c90zsdaj1yph4m8bt', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:18:25', 0); +INSERT INTO `pear_task_member` VALUES (212, 'nqrleu2c90zsdaj1yph4m8bt', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:25', 1); +INSERT INTO `pear_task_member` VALUES (213, 'mix3cg2eh1u60fknd7yz9v5t', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:18:37', 0); +INSERT INTO `pear_task_member` VALUES (214, 'mix3cg2eh1u60fknd7yz9v5t', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:37', 1); +INSERT INTO `pear_task_member` VALUES (215, 'dckxz1vpujtafshgr20mwo7e', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:18:45', 0); +INSERT INTO `pear_task_member` VALUES (216, 'dckxz1vpujtafshgr20mwo7e', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:45', 1); +INSERT INTO `pear_task_member` VALUES (217, 'fd1avskez2q43w80xhb7ypc9', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:18:53', 0); +INSERT INTO `pear_task_member` VALUES (218, 'fd1avskez2q43w80xhb7ypc9', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:53', 1); +INSERT INTO `pear_task_member` VALUES (219, 'as2y4r6mwxuhgvncop3f8z90', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:01', 1); +INSERT INTO `pear_task_member` VALUES (220, '8zj3vpx0b7qud24ylfgces1m', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:19:08', 0); +INSERT INTO `pear_task_member` VALUES (221, '8zj3vpx0b7qud24ylfgces1m', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:08', 1); +INSERT INTO `pear_task_member` VALUES (222, 'hcrdvbuzwgojst2f0p134qxi', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:19:18', 0); +INSERT INTO `pear_task_member` VALUES (223, 'hcrdvbuzwgojst2f0p134qxi', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:18', 1); +INSERT INTO `pear_task_member` VALUES (224, 'lmognshqz21dbewcu9a3rx87', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:19:37', 0); +INSERT INTO `pear_task_member` VALUES (225, 'lmognshqz21dbewcu9a3rx87', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:37', 1); +INSERT INTO `pear_task_member` VALUES (226, 'n6ulc7ebxpqahi50dy9k1sgf', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:51', 1); +INSERT INTO `pear_task_member` VALUES (227, 'rqjng1kfcp4wyiamt6o23zbu', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:19:57', 0); +INSERT INTO `pear_task_member` VALUES (228, 'rqjng1kfcp4wyiamt6o23zbu', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:57', 1); +INSERT INTO `pear_task_member` VALUES (229, 'qsz65fvgi8hyx3e7bn14o9wm', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:20:05', 0); +INSERT INTO `pear_task_member` VALUES (230, 'qsz65fvgi8hyx3e7bn14o9wm', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:05', 1); +INSERT INTO `pear_task_member` VALUES (231, 'byiuxhn0v6sod4zap1t2fclr', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:20:12', 0); +INSERT INTO `pear_task_member` VALUES (232, 'byiuxhn0v6sod4zap1t2fclr', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:12', 1); +INSERT INTO `pear_task_member` VALUES (233, 'jxd3rpmay6qonsk1i8wg5e9u', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:20:27', 0); +INSERT INTO `pear_task_member` VALUES (234, 'jxd3rpmay6qonsk1i8wg5e9u', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:27', 1); +INSERT INTO `pear_task_member` VALUES (235, 'vmzeciodgbfp7ysu38tq10kj', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:33', 1); +INSERT INTO `pear_task_member` VALUES (236, '6cagd725tifonvw0qphe9zsb', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:45', 1); +INSERT INTO `pear_task_member` VALUES (237, 'xu3jgyow2s9f1km0rctqin4v', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:20:54', 0); +INSERT INTO `pear_task_member` VALUES (238, 'xu3jgyow2s9f1km0rctqin4v', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:54', 1); +INSERT INTO `pear_task_member` VALUES (239, 'k3g07m1qyctvbp95siohju6f', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:21:12', 0); +INSERT INTO `pear_task_member` VALUES (240, 'k3g07m1qyctvbp95siohju6f', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:12', 1); +INSERT INTO `pear_task_member` VALUES (241, 'oh5wpj9kd8e6ltusxq271ma3', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:21:18', 0); +INSERT INTO `pear_task_member` VALUES (242, 'oh5wpj9kd8e6ltusxq271ma3', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:18', 1); +INSERT INTO `pear_task_member` VALUES (243, 'akdwslbtp3z82xecui0y4ovq', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:21:25', 0); +INSERT INTO `pear_task_member` VALUES (244, 'akdwslbtp3z82xecui0y4ovq', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:25', 1); +INSERT INTO `pear_task_member` VALUES (245, 'hayfr6vl398nq5exgszobu2j', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:31', 1); +INSERT INTO `pear_task_member` VALUES (246, 'mf80iu15kepavbg2r9ldcjsh', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:21:42', 0); +INSERT INTO `pear_task_member` VALUES (247, 'mf80iu15kepavbg2r9ldcjsh', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:42', 1); +INSERT INTO `pear_task_member` VALUES (248, 'nzy71f5i6g0skwau4lrj3d8b', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:22:08', 0); +INSERT INTO `pear_task_member` VALUES (249, 'nzy71f5i6g0skwau4lrj3d8b', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:08', 1); +INSERT INTO `pear_task_member` VALUES (250, '4cug3e5rodalq9x81ywht0zn', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:22:20', 0); +INSERT INTO `pear_task_member` VALUES (251, '4cug3e5rodalq9x81ywht0zn', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:20', 1); +INSERT INTO `pear_task_member` VALUES (252, '92fow0le47htb6xkv5ynzuri', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:22:24', 0); +INSERT INTO `pear_task_member` VALUES (253, '92fow0le47htb6xkv5ynzuri', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:24', 1); +INSERT INTO `pear_task_member` VALUES (254, '6ky18i9cg0eqvfzn2th3ux5l', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:34', 1); +INSERT INTO `pear_task_member` VALUES (255, 'zj6skt9orn748gh5mvb2ueif', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:40', 1); +INSERT INTO `pear_task_member` VALUES (256, 'a75dcqx2sjivokmg49yh380l', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:13', 1); +INSERT INTO `pear_task_member` VALUES (257, '7ns924ofulpjxkgq06y3bm5r', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:19', 1); +INSERT INTO `pear_task_member` VALUES (258, 'up6hn9bd34c8mglwaj1ytefz', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:02', 0); +INSERT INTO `pear_task_member` VALUES (259, 'as2y4r6mwxuhgvncop3f8z90', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:13', 0); +INSERT INTO `pear_task_member` VALUES (260, 'vmzeciodgbfp7ysu38tq10kj', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:25', 0); +INSERT INTO `pear_task_member` VALUES (261, '6cagd725tifonvw0qphe9zsb', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:38:30', 0); +INSERT INTO `pear_task_member` VALUES (262, 'zj6skt9orn748gh5mvb2ueif', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:45', 0); + +-- ---------------------------- +-- Table structure for pear_task_stages +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_stages`; +CREATE TABLE `pear_task_stages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', + `project_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '项目id', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 72 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务列表表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_task_stages +-- ---------------------------- +INSERT INTO `pear_task_stages` VALUES (33, '修复', 'mo4uqwfb06dxv8ez2spkl3rg', 33, NULL, '2018-12-25 07:20:36', '7z8tgb6xevy2aj9nui5fk0w1', 0); +INSERT INTO `pear_task_stages` VALUES (34, '重构', 'mo4uqwfb06dxv8ez2spkl3rg', 34, NULL, '2018-12-25 07:20:36', 'g0yw3r54qahbk7lets6fv2on', 0); +INSERT INTO `pear_task_stages` VALUES (35, '升级', 'mo4uqwfb06dxv8ez2spkl3rg', 35, NULL, '2018-12-25 07:20:36', 'psemnf3ugo89vc5r2hkxid1t', 0); +INSERT INTO `pear_task_stages` VALUES (36, '优化', 'mo4uqwfb06dxv8ez2spkl3rg', 37, NULL, '2018-12-25 07:20:36', 'p56enm7zck4id2rb0tx9lguh', 0); +INSERT INTO `pear_task_stages` VALUES (37, '新增', 'mo4uqwfb06dxv8ez2spkl3rg', 36, NULL, '2018-12-25 07:20:36', 'jvyswuxz34qk2cpt9o7ldb60', 0); +INSERT INTO `pear_task_stages` VALUES (38, '协议签订', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:35:47', 'oijw2ds86lf7zp1chvq03r5e', 0); +INSERT INTO `pear_task_stages` VALUES (39, '图纸设计', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:35:53', 'ipzyscgfo5l1qvah2xm4638t', 0); +INSERT INTO `pear_task_stages` VALUES (40, '评审及打样', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:21', '3uz8afjkxnogwivd9s0lqp7y', 0); +INSERT INTO `pear_task_stages` VALUES (41, '构件采购', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:34', 'f6dp4ur1zc2omswtyhnbixe3', 0); +INSERT INTO `pear_task_stages` VALUES (42, '制造安装', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:40', 'imltk4y2se1rbzafohw8x5p6', 0); +INSERT INTO `pear_task_stages` VALUES (43, '内部检验', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:45', 'miwt9bd6saxge2pvn31h0zky', 0); +INSERT INTO `pear_task_stages` VALUES (44, '测试', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:52', 'ao18rjcszwh2bm6inypxgkv0', 0); +INSERT INTO `pear_task_stages` VALUES (48, '产品计划', 'p94ckbwv5lyxt2rhzeam3s86', 0, NULL, '2019-01-02 11:17:27', 'xt4ne81fu9jgayw2szokr5q3', 0); +INSERT INTO `pear_task_stages` VALUES (49, '即将发布', 'p94ckbwv5lyxt2rhzeam3s86', 1, NULL, '2019-01-02 11:17:27', 'c9ro6jxpl25wbmuvfy840kqe', 0); +INSERT INTO `pear_task_stages` VALUES (50, '测试', 'p94ckbwv5lyxt2rhzeam3s86', 2, NULL, '2019-01-02 11:17:27', 'inxmfhz8kvqes1w39a2oc5j6', 0); +INSERT INTO `pear_task_stages` VALUES (51, '准备发布', 'p94ckbwv5lyxt2rhzeam3s86', 3, NULL, '2019-01-02 11:17:27', '2zm1n3g8ikaseow5cfp9h7dx', 0); +INSERT INTO `pear_task_stages` VALUES (52, '发布成功', 'p94ckbwv5lyxt2rhzeam3s86', 4, NULL, '2019-01-02 11:17:27', '3jdozmqf4tcakyixgn750ule', 0); +INSERT INTO `pear_task_stages` VALUES (53, '产品计划', '8ulzfth64cd0k1x5peivowm2', 0, NULL, '2019-01-03 09:15:11', 'pfi2ltmjhxuda90ncsgb5vwo', 0); +INSERT INTO `pear_task_stages` VALUES (54, '即将发布', '8ulzfth64cd0k1x5peivowm2', 2, NULL, '2019-01-03 09:15:11', 'ht0gfnevaq7kp3ldx16i82yj', 0); +INSERT INTO `pear_task_stages` VALUES (55, '测试', '8ulzfth64cd0k1x5peivowm2', 1, NULL, '2019-01-03 09:15:11', 'dot8li21nx437ypksjav59wf', 0); +INSERT INTO `pear_task_stages` VALUES (56, '准备发布', '8ulzfth64cd0k1x5peivowm2', 3, NULL, '2019-01-03 09:15:11', 'p0re71zhm48yxq63lfnjwkso', 0); +INSERT INTO `pear_task_stages` VALUES (57, '发布成功', '8ulzfth64cd0k1x5peivowm2', 4, NULL, '2019-01-03 09:15:11', 'k436eltf5zygbpnhrdqc8mo2', 0); +INSERT INTO `pear_task_stages` VALUES (61, 'ADD', 'elqa703jyvfhpt1dsxkzi8on', 61, NULL, '2019-01-04 21:15:46', '2sf7h3p01l5qgdeumrzny4bi', 0); +INSERT INTO `pear_task_stages` VALUES (62, 'Fix', 'elqa703jyvfhpt1dsxkzi8on', 62, NULL, '2019-01-04 21:15:51', 'njd4er1ohakl6bz258qcfgsv', 0); +INSERT INTO `pear_task_stages` VALUES (63, 'Change', 'elqa703jyvfhpt1dsxkzi8on', 63, NULL, '2019-01-04 21:16:07', 'oxcj9krmqeu08wbga2ftz7ls', 0); +INSERT INTO `pear_task_stages` VALUES (64, 'Update', 'elqa703jyvfhpt1dsxkzi8on', 64, NULL, '2019-01-04 21:16:29', 'sft603lxe5phk89ou1cgmiby', 0); +INSERT INTO `pear_task_stages` VALUES (65, 'Removed', 'elqa703jyvfhpt1dsxkzi8on', 65, NULL, '2019-01-04 21:16:49', '0jmqucy41h3rt9ag27wils6b', 0); +INSERT INTO `pear_task_stages` VALUES (66, '产品计划', 'gbim9jpevkh7qr6ufa1t3wl4', 0, NULL, '2019-01-05 21:57:31', 'j3f52swoct7earzhd6gxk41m', 0); +INSERT INTO `pear_task_stages` VALUES (67, '即将发布', 'gbim9jpevkh7qr6ufa1t3wl4', 1, NULL, '2019-01-05 21:57:31', '5fkwydvzopqrmxj0174nl93u', 0); +INSERT INTO `pear_task_stages` VALUES (68, '测试', 'gbim9jpevkh7qr6ufa1t3wl4', 2, NULL, '2019-01-05 21:57:31', '97gxmwyidlae4r2u1hqbcpnz', 0); +INSERT INTO `pear_task_stages` VALUES (69, '准备发布', 'gbim9jpevkh7qr6ufa1t3wl4', 3, NULL, '2019-01-05 21:57:31', '9f4vdsw7gzpo2hm1qbt0xyn6', 0); +INSERT INTO `pear_task_stages` VALUES (70, '发布成功', 'gbim9jpevkh7qr6ufa1t3wl4', 4, NULL, '2019-01-05 21:57:31', 'pm8129iltue7jnyvgb4d30xw', 0); + +-- ---------------------------- +-- Table structure for pear_task_stages_template +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_stages_template`; +CREATE TABLE `pear_task_stages_template` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', + `project_template_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '项目id', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `sort` int(11) NULL DEFAULT 0, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 84 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务列表模板表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_task_stages_template +-- ---------------------------- +INSERT INTO `pear_task_stages_template` VALUES (61, '待处理', 'un6125mxt4dcizhjqwvgyb3a', '2018-12-24 16:57:49', 1, 'ts0cdj5wzxnlhfymrvuk1ei9'); +INSERT INTO `pear_task_stages_template` VALUES (62, '进行中', 'un6125mxt4dcizhjqwvgyb3a', '2018-12-24 16:57:49', 0, '9vbjag5rl0ikxqyc146p7swf'); +INSERT INTO `pear_task_stages_template` VALUES (63, '已完成', 'un6125mxt4dcizhjqwvgyb3a', '2018-12-24 16:57:49', 0, 'l3o95r4wyk18bh2aq7xcz0ve'); +INSERT INTO `pear_task_stages_template` VALUES (65, '协议签订', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:33', 0, '4510enfyvjtzho3cw28xsagi'); +INSERT INTO `pear_task_stages_template` VALUES (66, '图纸设计', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:38', 0, '3esu4p172alok89wjmrvqihz'); +INSERT INTO `pear_task_stages_template` VALUES (67, '评审及打样', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:43', 0, 'e6jp81o7drfkzluxbhmiaqtv'); +INSERT INTO `pear_task_stages_template` VALUES (68, '构件采购', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:52', 0, 'tpy76njoair0clhz9xmeg482'); +INSERT INTO `pear_task_stages_template` VALUES (69, '制造安装', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:58', 0, 've97pldtbnjrqco1hyfx82sa'); +INSERT INTO `pear_task_stages_template` VALUES (70, '内部检验', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:01:04', 0, '4phrcltwygziu2s13jxbaqv8'); +INSERT INTO `pear_task_stages_template` VALUES (71, '验收', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:01:09', 0, 'qxi9n42p0w57jtrmyhz8gl3c'); +INSERT INTO `pear_task_stages_template` VALUES (72, '需求收集', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:01:30', 0, '48h13usk7en6ljyxbqgiw02z'); +INSERT INTO `pear_task_stages_template` VALUES (73, '评估确认', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:17', 0, '70z1fpxytvchbadkgsieowuj'); +INSERT INTO `pear_task_stages_template` VALUES (74, '需求暂缓', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:22', 0, 'bkyunf9jr2c37m4oi81sxzqp'); +INSERT INTO `pear_task_stages_template` VALUES (75, '研发中', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:27', 0, 'zu0vrhpoi835klgxqndmf6w9'); +INSERT INTO `pear_task_stages_template` VALUES (76, '内测中', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:32', 0, 'j4d5l7s6rgvk9o32ayt1uefc'); +INSERT INTO `pear_task_stages_template` VALUES (77, '通知用户', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:40', 0, 'cjk6al7f2ygp39des148iwzh'); +INSERT INTO `pear_task_stages_template` VALUES (78, '已完成&归档', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:45', 0, 'vn6dxyzme1g8ucbl3ikq0awt'); +INSERT INTO `pear_task_stages_template` VALUES (79, '产品计划', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:03', 0, '3atxfsv5rhz64pk8jl0enqd2'); +INSERT INTO `pear_task_stages_template` VALUES (80, '即将发布', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:09', 0, '1nucptea9b2vl7yfj8xgz4d6'); +INSERT INTO `pear_task_stages_template` VALUES (81, '测试', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:13', 0, 'pfidejaq2vn653h8zmsytrlb'); +INSERT INTO `pear_task_stages_template` VALUES (82, '准备发布', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:17', 0, 'uc1etmw4k5gys8jfpdbo7zrh'); +INSERT INTO `pear_task_stages_template` VALUES (83, '发布成功', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:23', 0, 'rmutqozd51shfp4w70n96iel'); + +-- ---------------------------- +-- Table structure for pear_task_tag +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_tag`; +CREATE TABLE `pear_task_tag` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `project_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名', + `color` enum('blue','red','orange','green','brown','purple') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'blue' COMMENT '颜色', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `color_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_task_tag +-- ---------------------------- +INSERT INTO `pear_task_tag` VALUES (1, NULL, '1292', 'dd', 'orange', '2018-07-27 16:06:08', '#ff9900'); +INSERT INTO `pear_task_tag` VALUES (2, NULL, '1292', 'ffs', 'green', '2018-07-27 16:06:14', '#19be6b'); +INSERT INTO `pear_task_tag` VALUES (3, NULL, '1292', '11', 'blue', '2018-09-07 23:22:11', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (4, NULL, '1292', '22', 'blue', '2018-09-07 23:22:12', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (5, NULL, '1292', '33', 'blue', '2018-09-07 23:22:13', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (6, NULL, '1292', '44', 'blue', '2018-09-07 23:22:14', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (7, NULL, '1292', '55', 'blue', '2018-09-07 23:22:15', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (8, NULL, '1292', '6666666666666', 'blue', '2018-09-07 23:22:17', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (9, NULL, '1292', '777777777', 'blue', '2018-09-07 23:22:19', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (10, NULL, '1292', '8888888', 'blue', '2018-09-07 23:22:22', '#2d8cf0'); + +-- ---------------------------- +-- Table structure for pear_task_to_tag +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_to_tag`; +CREATE TABLE `pear_task_to_tag` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `task_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `tag_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 95 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Fixed; + +-- ---------------------------- +-- Table structure for pear_user_token +-- ---------------------------- +DROP TABLE IF EXISTS `pear_user_token`; +CREATE TABLE `pear_user_token` ( + `token_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '令牌编号', + `user_id` int(10) UNSIGNED NOT NULL COMMENT '用户编号', + `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名', + `token` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录令牌', + `login_time` int(10) UNSIGNED NOT NULL COMMENT '登录时间', + `client_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客户端类型 android wap', + `login_ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登录ip', + PRIMARY KEY (`token_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'PC端登录令牌表' ROW_FORMAT = Compact; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/data/install.lock b/data/install.lock new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/data/install.lock @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/data/pearproject.sql b/data/pearproject.sql new file mode 100644 index 0000000..353a869 --- /dev/null +++ b/data/pearproject.sql @@ -0,0 +1,2568 @@ +/* + Navicat Premium Data Transfer + + Source Server : 本地 + Source Server Type : MySQL + Source Server Version : 50642 + Source Host : 127.0.0.1:3306 + Source Schema : pearproject + + Target Server Type : MySQL + Target Server Version : 50642 + File Encoding : 65001 + + Date: 16/01/2019 15:30:27 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +-- ---------------------------- +-- Table structure for pear_build +-- ---------------------------- +DROP TABLE IF EXISTS `pear_build`; +CREATE TABLE `pear_build` ( + `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `product` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `branch` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `project` mediumint(8) UNSIGNED NOT NULL DEFAULT 0, + `name` char(150) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `scmPath` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `filePath` char(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `date` date NOT NULL, + `stories` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `bugs` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `builder` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '', + `desc` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `deleted` enum('0','1') CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '0', + PRIMARY KEY (`id`) USING BTREE, + INDEX `build`(`product`, `project`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 75 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '版本表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_build +-- ---------------------------- +INSERT INTO `pear_build` VALUES (73, 0, 0, 1292, '2.13.0 King of Opera', '', '', '2018-04-30', '', '', 'admin', '
  • Table 列新增属性 minWidth 和 maxWidth#3284
  • DatePicker 的 disabledDate 功能,现在也能限制时、分、秒了。#3246
  • 优化 Table 筛选样式。#3206
  • 修复 Table 在多级表头里使用过滤和排序的 bug。#3339
  • 修复 Table 在 2.12.0 版本,设置 show-header=\"false\" 报错的 bug。
  • 修复 Poptip 和 Tooltip 有时方向识别错误的 bug,并支持自定义 popper.js 的 options 选项。
  • 修复 DatePicker 在 daterange 模式下,选择年、月后显示值不正确的 bug。#3345
  • 修复 DatePicker 在 Safari 浏览器下 on-change 事件返回值有时不正确的 bug。#3232
  • 修复 DatePicker 的 show-week-numbers 无法动态设置的 bug。#3277
', '0'); +INSERT INTO `pear_build` VALUES (74, 0, 0, 1292, '2.13.1', '', '', '2018-04-30', '', '', 'admin', '
  • Tag 新增属性 fade
  • InputNumber 新增属性 placeholder#3424
  • InputNumber 的事件 on-focus 增加返回值 event。#3395
  • DatePicker 的事件 on-change 增加返回值 type。#3353
  • 优化 popper.js 的配置及 dropdown 的展开动画。#3354
  • 修复 Table 在动态调整页面宽度,有时滚动条显示错误的 bug。#3358
  • 修复 Poptip / Tooltip 动态修改内容后,位置计算不准确的 bug。#3412
  • 修复在 Form 内使用 Carousel 时,点击按钮会跳转的问题。#3426
', '0'); + +-- ---------------------------- +-- Table structure for pear_collection +-- ---------------------------- +DROP TABLE IF EXISTS `pear_collection`; +CREATE TABLE `pear_collection` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '类型', + `source_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务ID', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 113 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '收藏表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_collection +-- ---------------------------- +INSERT INTO `pear_collection` VALUES (108, 'aut9wrz1pn0elf5s47ivx26o', 'task', 'aut9wrz1pn0elf5s47ivx26o', '6v7be19pwman2fird04gqu53', '2018-12-30 17:14:23'); +INSERT INTO `pear_collection` VALUES (109, 'mv4usefb06dxv8ez2spkl223', 'task', 'mv4usefb06dxv8ez2spkl223', '6v7be19pwman2fird04gqu53', '2018-12-30 22:16:05'); +INSERT INTO `pear_collection` VALUES (110, 'twb8f52jasn9vry6iko0dqg4', 'task', 'twb8f52jasn9vry6iko0dqg4', '6v7be19pwman2fird04gqu53', '2019-01-13 19:44:34'); +INSERT INTO `pear_collection` VALUES (112, 'na6uwxzi2fg3sre5qy9vb8m1', NULL, 'uwq87z2f0hnvrl6o9gtcb3iy', '6v7be19pwman2fird04gqu53', '2019-01-13 20:47:37'); + +-- ---------------------------- +-- Table structure for pear_department +-- ---------------------------- +DROP TABLE IF EXISTS `pear_department`; +CREATE TABLE `pear_department` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `organization_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织编号', + `name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `pcode` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '上级编号', + `icon` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '图标', + `create_time` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `path` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '上级路径', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '部门表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_department +-- ---------------------------- +INSERT INTO `pear_department` VALUES (1, '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '技术部', 0, '', NULL, NULL, NULL); +INSERT INTO `pear_department` VALUES (2, '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '开发1部', 0, '6v7be19pwman2fird04gqu53', NULL, NULL, '6v7be19pwman2fird04gqu53'); +INSERT INTO `pear_department` VALUES (3, 'pn6fyumbd9clz0t32kxr1qj8', 'bhlmq6n5edixkwct17a2gpv3', '太阳总部', 0, '', NULL, '2019-01-13 11:09:58', ''); +INSERT INTO `pear_department` VALUES (4, 'szdc5ojkgb802urvyxah196e', 'bhlmq6n5edixkwct17a2gpv3', '黑子1部', 0, 'pn6fyumbd9clz0t32kxr1qj8', NULL, '2019-01-13 11:10:24', 'pn6fyumbd9clz0t32kxr1qj8'); + +-- ---------------------------- +-- Table structure for pear_department_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_department_member`; +CREATE TABLE `pear_department_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT 'id', + `department_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '部门id', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织id', + `account_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `join_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加入时间', + `is_principal` tinyint(1) NULL DEFAULT NULL COMMENT '是否负责人', + `is_owner` tinyint(1) NULL DEFAULT 0 COMMENT '拥有者', + `authorize` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 39 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '部门-成员表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_department_member +-- ---------------------------- +INSERT INTO `pear_department_member` VALUES (34, 'fze7qr03v1dhtaygpjco9254', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu22', '2019-01-07 09:36:39', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (35, 'tjf432lxcuoizvhk95rgm78w', '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu22', '2019-01-07 09:37:08', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (36, '2tyvcl53bdr1a9h4ofxuepg8', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu55', '2019-01-07 09:48:37', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (37, 'gd6f5a8qmzor239kvlixn071', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu11', '2019-01-07 09:49:03', 0, 0, NULL); +INSERT INTO `pear_department_member` VALUES (38, 'uzodyahgnc5pqk1iv2sef86x', '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu55', '2019-01-07 09:52:29', 0, 0, NULL); + +-- ---------------------------- +-- Table structure for pear_file +-- ---------------------------- +DROP TABLE IF EXISTS `pear_file`; +CREATE TABLE `pear_file` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '编号', + `path_name` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '相对路径', + `title` char(90) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `extension` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '扩展名', + `size` mediumint(8) UNSIGNED NULL DEFAULT 0 COMMENT '文件大小', + `object_type` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '对象类型', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织编码', + `task_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务编码', + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '项目编码', + `create_by` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '上传人', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `downloads` mediumint(8) UNSIGNED NULL DEFAULT 0 COMMENT '下载次数', + `extra` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '额外信息', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '删除标记', + `file_url` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '完整地址', + `file_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件类型', + `deleted_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '删除时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 44 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '文件表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_file +-- ---------------------------- +INSERT INTO `pear_file` VALUES (34, 'lhp9dfz831jquoam6g4nbery', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-ioncube_loaders_win_nonts_vc11_x86.zip', 'ioncube_loaders_win_nonts_vc11_x86', 'zip', 793854, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-ioncube_loaders_win_nonts_vc11_x86.zip', 'application/x-zip-compressed', ''); +INSERT INTO `pear_file` VALUES (35, 'lr08qzj5bucy2p1osinhkdef', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-技术部项目周报.xlsx', '技术部项目周报', 'xlsx', 8, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104540-技术部项目周报.xlsx', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', ''); +INSERT INTO `pear_file` VALUES (36, 'gf1zm573upka8htwlydsjxcr', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104607-cover.png', 'cover', 'png', 99467, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-11 10:46:07', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190111/20190111104607-cover.png', 'image/png', ''); +INSERT INTO `pear_file` VALUES (37, '0vr64cpylg3sbhkuij8a2en5', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-2.jpg', '2', 'jpg', 176083, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:05', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-2.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (38, 'qr18x4eja9vs35ftudck7w2m', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-1.jpg', '1', 'jpg', 157751, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:05', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-1.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (39, 'txu5z7rg6bavnk4h3y8wq9i2', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-3.jpg', '3', 'jpg', 150189, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:05', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184705-3.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (40, 'dqkx4o6wp2r9uzt15fyaenlv', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184741-5a1d10000fc8c.jpg', '5a1d10000fc8c', 'jpg', 445137, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:41', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184741-5a1d10000fc8c.jpg', 'image/jpeg', ''); +INSERT INTO `pear_file` VALUES (42, '7ru54lhm6i198stqkdcy3ap2', 'static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184757-05990022176026337.jpg', '05990022176026337', 'jpg', 45930, '', '6v7be19pwman2fird04gqu53', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-12 18:47:57', 0, '', 0, 'http://easyproject.net/static/upload/file/default/6v7be19pwman2fird04gqu53/6v7be19pwman2fird04gqu53/20190112/20190112184757-05990022176026337.jpg', 'image/jpeg', '2019-01-12 22:26:56'); +INSERT INTO `pear_file` VALUES (43, 'tfydkno68i4b7ha0q1x2uwcs', 'static/upload/file/default/bh5mdpzy7wg46kiqx9uclns2/6v7be19pwman2fird04gqu53/20190113/20190113122337-avatar.png', 'avatar', 'png', 51574, '', 'bh5mdpzy7wg46kiqx9uclns2', NULL, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2019-01-13 12:23:37', 0, '', 0, 'http://easyproject.net/static/upload/file/default/bh5mdpzy7wg46kiqx9uclns2/6v7be19pwman2fird04gqu53/20190113/20190113122337-avatar.png', 'image/png', ''); + +-- ---------------------------- +-- Table structure for pear_lock +-- ---------------------------- +DROP TABLE IF EXISTS `pear_lock`; +CREATE TABLE `pear_lock` ( + `pid` bigint(20) UNSIGNED NOT NULL COMMENT 'IP+TYPE', + `pvalue` tinyint(3) UNSIGNED NOT NULL DEFAULT 1 COMMENT '次数', + `expiretime` int(11) NOT NULL DEFAULT 0 COMMENT '锁定截止时间', + PRIMARY KEY (`pid`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '防灌水表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_lock +-- ---------------------------- +INSERT INTO `pear_lock` VALUES (21307064333, 2, 1475226020); + +-- ---------------------------- +-- Table structure for pear_mailqueue +-- ---------------------------- +DROP TABLE IF EXISTS `pear_mailqueue`; +CREATE TABLE `pear_mailqueue` ( + `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `toList` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `ccList` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `subject` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `body` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `addedBy` char(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + `addedDate` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `sendTime` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT 'wait', + `failReason` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL, + PRIMARY KEY (`id`) USING BTREE, + INDEX `sendTime`(`sendTime`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 31858 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '邮件队列' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for pear_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_member`; +CREATE TABLE `pear_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '系统前台用户表', + `account` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '用户登陆账号', + `password` char(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '登陆密码,32位加密串', + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '用户昵称', + `mobile` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机', + `realname` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '真实姓名', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态', + `last_login_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上次登录时间', + `sex` char(2) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '性别', + `avatar` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '头像', + `idcard` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '身份证', + `province` int(11) NULL DEFAULT 0 COMMENT '省', + `city` int(11) NULL DEFAULT 0 COMMENT '市', + `area` int(11) NULL DEFAULT 0 COMMENT '区', + `address` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '所在地址', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `email` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `code` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编号', + PRIMARY KEY (`id`) USING BTREE, + INDEX `username`(`account`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 589 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_member +-- ---------------------------- +INSERT INTO `pear_member` VALUES (586, 'Alians', 'e10adc3949ba59abbe56e057f20f883e', 'Alians', '18377893857', 'vilson', NULL, 1, '2019-01-04 21:23:23', '', 'https://gw.alipayobjects.com/zos/rmsportal/zOsKZmFRdUtvpqCImOVY.png', NULL, 0, 0, 0, NULL, NULL, 'vilson@qq.com', 'kqdcn2w40p58r31zyo6efjib'); +INSERT INTO `pear_member` VALUES (582, '123456', 'e10adc3949ba59abbe56e057f20f883e', 'vilson', '18681140825', 'juli', NULL, 1, '2019-01-16 14:32:39', '', 'https://static.vilson.xyz/cover.png', '', 0, 0, 0, NULL, NULL, '545522390@qq.com', '6v7be19pwman2fird04gqu53'); +INSERT INTO `pear_member` VALUES (587, 'Chihiro', 'e10adc3949ba59abbe56e057f20f883e', 'Chihiro', '18278881051', 'Chihiro', NULL, 1, '2019-01-04 21:28:53', '', 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', NULL, 0, 0, 0, NULL, NULL, '741648282@qq.com', 'y680trgedcavbhnz24u7i5m3'); +INSERT INTO `pear_member` VALUES (588, 'Json', 'f9f02f39d6d2048d760d8add98265ba1', 'Json', '18681140821', 'Json', '2019-01-05 21:57:01', 1, '2019-01-06 08:21:42', '', 'https://static.vilson.xyz/cover.png', NULL, 0, 0, 0, NULL, NULL, '123456@qq.com', 'vys8gd32cfui6brtwzj4pqho'); + +-- ---------------------------- +-- Table structure for pear_member_account +-- ---------------------------- +DROP TABLE IF EXISTS `pear_member_account`; +CREATE TABLE `pear_member_account` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `member_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '所属账号id', + `organization_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '所属组织', + `department_code` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '部门编号', + `authorize` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色', + `is_owner` tinyint(1) NULL DEFAULT 0 COMMENT '是否主账号', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名', + `mobile` varchar(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机号码', + `email` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮件', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `last_login_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '上次登录时间', + `status` tinyint(1) NULL DEFAULT 0 COMMENT '状态', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `position` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '职位', + `department` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '部门', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 31 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '组织账号表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_member_account +-- ---------------------------- +INSERT INTO `pear_member_account` VALUES (21, '6v7be19pwman2fird04gqu11', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '4', 1, 'vilson', '18681140825', '545522390@qq.com', NULL, NULL, 1, NULL, 'http://easyproject.net/static/upload/member/avatar/20181221/c3438c50fbc1b19607949893b42abee8.png', '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (22, '6v7be19pwman2fird04gqu22', 'kqdcn2w40p58r31zyo6efjib', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53,6v7be19pwman2fird04gqu11', '4', 0, 'Alians', '18377893857', 'vilson@qq.com', NULL, NULL, 1, 'eee', 'http://easyproject.net/static/upload/member/avatar/20181220/b417990ee8b131c2467ceda3e93ea8cf.jpg', NULL, NULL); +INSERT INTO `pear_member_account` VALUES (28, '0n52te9psyukd1g84frajwzv', '6v7be19pwman2fird04gqu53', 'bh5mdpzy7wg46kiqx9uclns2', '', NULL, 1, 'vilson', NULL, '545522390@qq.com', '2019-01-13 10:24:47', NULL, 1, NULL, 'https://static.vilson.xyz/cover.png', '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (24, '6v7be19pwman2fird04gqu44', 'kqdcn2w40p58r31zyo6efjib', '6v7be19pwman2fird04gqsss', '', NULL, 1, 'Alians', '18377893857', 'vilson@qq.com', NULL, NULL, 1, NULL, 'http://easyproject.net/static/upload/member/avatar/20181220/b417990ee8b131c2467ceda3e93ea8cf.jpg', NULL, NULL); +INSERT INTO `pear_member_account` VALUES (25, '6v7be19pwman2fird04gqu55', 'y680trgedcavbhnz24u7i5m3', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53,6v7be19pwman2fird04gqu11', '4', 0, 'Chihiro', '18278881051', '741648282@qq.com', NULL, NULL, 1, 'eee', 'https://gw.alipayobjects.com/zos/rmsportal/BiazfanxmamNRoxxVxka.png', NULL, NULL); +INSERT INTO `pear_member_account` VALUES (26, '6v7be19pwman2fird04gqu66', 'vys8gd32cfui6brtwzj4pqho', '4ni58wts2egcybvodfh1kmaj', '', NULL, 1, 'Json', NULL, '123456@qq.com', '2019-01-05 21:57:01', NULL, 1, NULL, NULL, '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (30, 'if34h2lvdu06twxce1npmog7', 'vys8gd32cfui6brtwzj4pqho', '6v7be19pwman2fird04gqu53', '', '4', 0, 'Json', NULL, '123456@qq.com', '2019-01-16 15:18:29', NULL, 1, NULL, NULL, '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); +INSERT INTO `pear_member_account` VALUES (29, 'vg10jpez6w4odt87mnyfhax5', '6v7be19pwman2fird04gqu53', 'bhlmq6n5edixkwct17a2gpv3', '', NULL, 1, 'vilson', NULL, '545522390@qq.com', '2019-01-13 10:26:44', NULL, 1, NULL, 'https://static.vilson.xyz/cover.png', '资深工程师', '某某公司-某某某事业群-某某平台部-某某技术部-BM'); + +-- ---------------------------- +-- Table structure for pear_notify +-- ---------------------------- +DROP TABLE IF EXISTS `pear_notify`; +CREATE TABLE `pear_notify` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `title` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题', + `content` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '内容', + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '通知类型。通知:notice,消息:message,待办:task', + `from` int(11) NULL DEFAULT 0 COMMENT '发送人id', + `to` int(11) NULL DEFAULT 0 COMMENT '送达用户id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '生成时间', + `is_read` tinyint(1) NULL DEFAULT 0 COMMENT '是否已读', + `read_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '阅读时间', + `send_data` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '关联数据', + `finally_send_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '最终发送时间', + `send_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '发送时间', + `action` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'none' COMMENT '场景', + `terminal` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '推送终端。管理端:admin,移动端:wap', + `from_type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '\'landord\',\'admin\',\'system\'', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 4244 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '动态通知表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Table structure for pear_organization +-- ---------------------------- +DROP TABLE IF EXISTS `pear_organization`; +CREATE TABLE `pear_organization` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '头像', + `description` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `owner_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '拥有者', + `create_time` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `personal` tinyint(1) NULL DEFAULT 0 COMMENT '是否个人项目', + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '编号', + `address` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '地址', + `province` int(10) NULL DEFAULT 0 COMMENT '省', + `city` int(10) NULL DEFAULT 0 COMMENT '市', + `area` int(10) NULL DEFAULT 0 COMMENT '区', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '组织表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_organization +-- ---------------------------- +INSERT INTO `pear_organization` VALUES (1, 'vilson的个人项目', NULL, NULL, '6v7be19pwman2fird04gqu53', '2018-10-12', 1, '6v7be19pwman2fird04gqu53', NULL, 0, 0, 0); +INSERT INTO `pear_organization` VALUES (3, 'Alians的个人项目', NULL, NULL, 'kqdcn2w40p58r31zyo6efjib', '2018-10-12', 1, '6v7be19pwman2fird04gqsss', NULL, 0, 0, 0); +INSERT INTO `pear_organization` VALUES (4, 'Json的个人项目', NULL, NULL, 'vys8gd32cfui6brtwzj4pqho', '2019-01-05 21:57:01', 1, '4ni58wts2egcybvodfh1kmaj', NULL, 0, 0, 0); +INSERT INTO `pear_organization` VALUES (5, '星星联盟', NULL, NULL, '6v7be19pwman2fird04gqu53', '2019-01-13 10:24:42', 1, 'bh5mdpzy7wg46kiqx9uclns2', '星星联盟', 150000, 150300, 150303); +INSERT INTO `pear_organization` VALUES (6, '太阳联盟', NULL, NULL, '6v7be19pwman2fird04gqu53', '2019-01-13 10:26:39', 1, 'bhlmq6n5edixkwct17a2gpv3', '太阳联盟', 140000, 140300, 140303); + +-- ---------------------------- +-- Table structure for pear_project +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project`; +CREATE TABLE `pear_project` ( + `id` mediumint(8) UNSIGNED NOT NULL AUTO_INCREMENT, + `cover` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '封面', + `name` varchar(90) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '名称', + `code` varchar(45) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编号', + `description` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '描述', + `access_control_type` enum('open','private','custom') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'open' COMMENT '访问控制l类型', + `white_list` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '可以访问项目的权限组(白名单)', + `order` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '排序', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '删除标记', + `template_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '项目类型', + `schedule` double(5, 2) NULL DEFAULT 0.00 COMMENT '进度', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织id', + `deleted_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除时间', + `private` tinyint(1) NULL DEFAULT 1 COMMENT '是否私有', + `prefix` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '项目前缀', + `open_prefix` tinyint(1) NULL DEFAULT 0 COMMENT '是否开启项目前缀', + `archive` tinyint(1) NULL DEFAULT 0 COMMENT '是否归档', + `archive_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '归档时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `project`(`order`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 13043 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_project +-- ---------------------------- +INSERT INTO `pear_project` VALUES (1, 'https://beta.vilson.xyz/static/upload//20190103/f9ad4e304ea0be7609e3236188f7547d.png', 'iView', 'a8mpr6tvbndk10hj2lwcqzuo', '那是一种内在的东西, 他们到达不了,也无法触及的', 'private', NULL, NULL, 0, '10', 39.00, '2018-04-30 22:29:18', '6v7be19pwman2fird04gqu53', NULL, 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (2, 'https://beta.vilson.xyz/static/upload//20190103/aaacec0e2001580b44dffbb967804349.png', 'Alipay', '8rlqyh56smzpoc1wef7390t2', '城镇中有那么多的酒馆,她却偏偏走进了我的酒馆', 'open', NULL, NULL, 0, '10', 75.00, '2018-05-01 09:28:36', '6v7be19pwman2fird04gqu53', NULL, 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (3, 'https://beta.vilson.xyz/static/upload//20190103/9ba2134d72cc3cec58f61024b89eb798.png', 'Vue', 'nkp4gulsb6oxqyi80fhead39', '生命就像一盒巧克力,结果往往出人意料', 'open', NULL, NULL, 0, '10', 63.00, '2018-05-01 09:33:43', '6v7be19pwman2fird04gqu53', '2019-01-03 22:20:10', 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (4, 'https://beta.vilson.xyz/static/upload//20190103/6fc14133651ee1c6ee1abaafcea76d01.png', 'Angular', 'sbklfvyouc0qpmwhitn47j5z', '希望是一个好东西,也许是最好的,好东西是不会消亡的', 'private', NULL, NULL, 0, '13', 100.00, '2018-05-01 09:36:05', '6v7be19pwman2fird04gqu53', NULL, 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (5, 'https://beta.vilson.xyz/static/upload//20190103/5d2a6e2d2cb235bb6888b884331bb516.png', 'EasyUI', 'n5opgqevrz1l03h48uwx67d2', '那时候我只会想自己想要什么,从不想自己拥有什么', 'open', NULL, NULL, 1, '0', 0.00, '2018-12-22 10:52:25', '6v7be19pwman2fird04gqu53', '2019-01-03 22:19:50', 1, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (1304, 'https://beta.vilson.xyz/static/upload//20190103/f5187655ceab8b52a335443664dffb3c.png', 'Vant', 'tnxpbov8kez6m4wl2hfjucd9', '现在的魏无羡,离开了蓝忘机就不行', 'open', NULL, 0, 0, '0', 50.00, '2018-12-23 08:31:53', '6v7be19pwman2fird04gqu53', '2019-01-04 11:33:02', 1, '', 0, 1, '2019-01-13 13:53:42'); +INSERT INTO `pear_project` VALUES (1303, 'https://beta.vilson.xyz/static/upload//20190103/30bdd62b610f5a4e3f788ec37e6c4a5b.png', 'Material UI', 'elqa703jyvfhpt1dsxkzi8on', '这个项目你不是项目成员,将不能进行操作(只读)', 'open', NULL, 0, 0, '0', 35.00, '2018-12-23 09:33:46', '6v7be19pwman2fird04gqu53', NULL, 0, '', 0, 0, NULL); +INSERT INTO `pear_project` VALUES (1302, 'https://beta.vilson.xyz/static/upload//20190103/271ec382566f0d2ca187740330b19a17.png', 'Ant Motion', 'ibag9hw3o1tusd5qlpxrk782', '如果我真的存在,也是因为你需要我', 'open', NULL, 0, 1, '0', 50.00, '2018-12-23 09:53:25', '6v7be19pwman2fird04gqu53', '2019-01-04 21:48:33', 0, '', 0, 0, '2019-01-02 21:01:12'); +INSERT INTO `pear_project` VALUES (1305, 'https://beta.vilson.xyz/static/upload//20190103/d86b104c0e1131b2fbd06dce615470df.png', 'Ant Design', 'mo4uqwfb06dxv8ez2spkl3rg', '那时候我只会想自己想要什么,从不想自己拥有什么', 'open', NULL, 0, 0, '0', 24.00, '2018-12-25 07:20:36', '6v7be19pwman2fird04gqu53', '2019-01-02 22:06:02', 1, 'EP', 0, 0, '2019-01-02 20:59:09'); +INSERT INTO `pear_project` VALUES (1307, 'https://beta.vilson.xyz/static/upload//20190103/271ec382566f0d2ca187740330b19a17.png', '测试', '8ulzfth64cd0k1x5peivowm2', '测试11', 'open', NULL, 0, 1, '', 0.00, '2019-01-03 09:15:11', '6v7be19pwman2fird04gqu53', '2019-01-03 22:18:30', 1, '', 0, 0, '2019-01-03 10:52:54'); +INSERT INTO `pear_project` VALUES (13042, 'http://easyproject.net/static/image/default/project-cover.png', 'OKR 管理', 'gbim9jpevkh7qr6ufa1t3wl4', 'OKR 管理', 'open', NULL, 0, 1, '', 0.00, '2019-01-05 21:57:31', '4ni58wts2egcybvodfh1kmaj', '2019-01-06 08:21:49', 1, NULL, 0, 0, NULL); + +-- ---------------------------- +-- Table structure for pear_project_auth +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_auth`; +CREATE TABLE `pear_project_auth` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `title` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '权限名称', + `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态(1:禁用,2:启用)', + `sort` smallint(6) UNSIGNED NULL DEFAULT 0 COMMENT '排序权重', + `desc` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '备注说明', + `create_by` bigint(11) UNSIGNED NULL DEFAULT 0 COMMENT '创建人', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '所属组织', + `is_default` tinyint(1) NULL DEFAULT 0 COMMENT '是否默认', + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '权限类型', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目权限表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_auth +-- ---------------------------- +INSERT INTO `pear_project_auth` VALUES (1, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', '', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (2, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', '', 1, 'member'); +INSERT INTO `pear_project_auth` VALUES (3, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', '6v7be19pwman2fird04gqu53', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (4, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', '6v7be19pwman2fird04gqu53', 1, 'member'); +INSERT INTO `pear_project_auth` VALUES (6, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', 'bh5mdpzy7wg46kiqx9uclns2', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (7, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', 'bh5mdpzy7wg46kiqx9uclns2', 1, 'member'); +INSERT INTO `pear_project_auth` VALUES (8, '管理员', 1, 0, '管理员', 0, '2018-08-01 14:20:46', 'bhlmq6n5edixkwct17a2gpv3', 0, 'admin'); +INSERT INTO `pear_project_auth` VALUES (9, '成员', 1, 0, '成员', 0, '2018-12-20 13:39:59', 'bhlmq6n5edixkwct17a2gpv3', 1, 'member'); + +-- ---------------------------- +-- Table structure for pear_project_auth_node +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_auth_node`; +CREATE TABLE `pear_project_auth_node` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `auth` bigint(20) UNSIGNED NULL DEFAULT NULL COMMENT '角色ID', + `node` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '节点路径', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_system_auth_auth`(`auth`) USING BTREE, + INDEX `index_system_auth_node`(`node`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4192 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目角色与节点绑定' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_auth_node +-- ---------------------------- +INSERT INTO `pear_project_auth_node` VALUES (3097, 1, 'project'); +INSERT INTO `pear_project_auth_node` VALUES (3098, 1, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (3099, 1, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3100, 1, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3101, 1, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (3102, 1, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3103, 1, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (3104, 1, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3105, 1, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3106, 1, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3107, 1, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3108, 1, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (3109, 1, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (3110, 1, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3111, 1, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3112, 1, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3113, 1, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (3114, 1, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (3115, 1, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (3116, 1, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3117, 1, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3118, 1, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (3119, 1, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3120, 1, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3121, 1, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (3122, 1, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3123, 1, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3124, 1, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3125, 1, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (3126, 1, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3127, 1, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3128, 1, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3129, 1, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3130, 1, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3131, 1, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3132, 1, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3133, 1, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3134, 1, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3135, 1, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3136, 1, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3137, 1, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (3138, 1, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (3139, 1, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (3140, 1, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (3141, 1, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (3142, 1, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (3143, 1, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3144, 1, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3145, 1, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (3146, 1, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (3147, 1, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3148, 1, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3149, 1, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3150, 1, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3151, 1, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3152, 1, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3153, 1, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3154, 1, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3155, 1, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3156, 1, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3157, 1, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3158, 1, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3159, 1, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3160, 1, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (3161, 1, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3162, 1, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3163, 1, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3164, 1, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3165, 1, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3166, 1, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3167, 1, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3168, 1, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3169, 1, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (3170, 1, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (3171, 1, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (3172, 1, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3173, 1, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3174, 1, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (3175, 1, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3176, 1, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3177, 1, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3178, 1, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (3179, 1, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3180, 1, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3181, 1, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3182, 1, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3183, 1, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3184, 1, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (3185, 1, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3186, 1, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3187, 1, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3188, 1, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3189, 1, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3190, 1, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3191, 1, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3192, 1, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3193, 1, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3194, 1, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3195, 1, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3196, 1, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3197, 1, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3198, 1, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3199, 1, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3200, 1, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3201, 1, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3202, 1, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (3203, 1, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3204, 1, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3205, 1, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3206, 1, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (3207, 1, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (3208, 1, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3209, 1, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3210, 1, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3211, 1, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (3212, 1, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3213, 1, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3214, 1, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (3215, 1, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3216, 1, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3217, 1, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3218, 1, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3219, 2, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3220, 2, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3221, 2, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3222, 2, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3223, 2, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3224, 2, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3225, 2, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3226, 2, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3227, 2, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3228, 2, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3229, 2, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3230, 2, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3231, 2, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3232, 2, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3233, 2, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3234, 2, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3235, 2, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3236, 2, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3237, 2, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3238, 2, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3239, 2, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3240, 2, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3241, 2, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3242, 2, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3243, 2, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3244, 2, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3245, 2, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3246, 2, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3247, 2, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3248, 2, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3249, 2, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3250, 2, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3251, 2, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3252, 2, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3253, 2, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3254, 2, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3255, 2, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3256, 2, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3257, 2, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3258, 2, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3259, 2, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3260, 2, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3261, 2, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3262, 2, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3263, 2, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3264, 2, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3265, 2, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3266, 2, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3267, 2, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3268, 2, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3269, 2, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3270, 2, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3271, 2, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3272, 2, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3273, 2, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3274, 2, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3275, 2, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3276, 2, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3636, 6, 'project'); +INSERT INTO `pear_project_auth_node` VALUES (3637, 6, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (3638, 6, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3639, 6, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3640, 6, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (3641, 6, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3642, 6, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (3643, 6, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3644, 6, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3645, 6, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3646, 6, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3647, 6, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (3648, 6, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (3649, 6, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3650, 6, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3651, 6, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3652, 6, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (3653, 6, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (3654, 6, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (3655, 6, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3656, 6, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3657, 6, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (3658, 6, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3659, 6, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3660, 6, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (3661, 6, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3662, 6, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3663, 6, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3664, 6, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (3665, 6, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3666, 6, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3667, 6, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3668, 6, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3669, 6, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3670, 6, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3671, 6, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3672, 6, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3673, 6, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3674, 6, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3675, 6, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3676, 6, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (3677, 6, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (3678, 6, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (3679, 6, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (3680, 6, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (3681, 6, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (3682, 6, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3683, 6, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3684, 6, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (3685, 6, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (3686, 6, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3687, 6, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3688, 6, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3689, 6, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3690, 6, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3691, 6, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3692, 6, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3693, 6, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3694, 6, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3695, 6, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3696, 6, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3697, 6, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3698, 6, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3699, 6, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (3700, 6, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3701, 6, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3702, 6, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3703, 6, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3704, 6, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3705, 6, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3706, 6, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3707, 6, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3708, 6, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (3709, 6, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (3710, 6, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (3711, 6, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3712, 6, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3713, 6, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (3714, 6, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3715, 6, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3716, 6, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3717, 6, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (3718, 6, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3719, 6, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3720, 6, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3721, 6, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3722, 6, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3723, 6, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (3724, 6, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3725, 6, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3726, 6, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3727, 6, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3728, 6, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3729, 6, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3730, 6, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3731, 6, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3732, 6, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3733, 6, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3734, 6, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3735, 6, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3736, 6, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3737, 6, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3738, 6, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3739, 6, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3740, 6, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3741, 6, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (3742, 6, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3743, 6, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3744, 6, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3745, 6, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (3746, 6, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (3747, 6, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3748, 6, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3749, 6, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3750, 6, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (3751, 6, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3752, 6, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3753, 6, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (3754, 6, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3755, 6, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3756, 6, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3757, 6, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3758, 7, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3759, 7, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3760, 7, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3761, 7, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3762, 7, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3763, 7, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3764, 7, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3765, 7, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3766, 7, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3767, 7, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3768, 7, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3769, 7, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3770, 7, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3771, 7, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3772, 7, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3773, 7, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3774, 7, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3775, 7, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3776, 7, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3777, 7, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3778, 7, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3779, 7, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3780, 7, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3781, 7, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3782, 7, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3783, 7, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3784, 7, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3785, 7, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3786, 7, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3787, 7, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3788, 7, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3789, 7, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3790, 7, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3791, 7, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3792, 7, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3793, 7, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3794, 7, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3795, 7, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3796, 7, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3797, 7, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3798, 7, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3799, 7, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3800, 7, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3801, 7, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3802, 7, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3803, 7, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3804, 7, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3805, 7, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3806, 7, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3807, 7, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3808, 7, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3809, 7, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3810, 7, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3811, 7, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3812, 7, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3813, 7, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3814, 7, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3815, 7, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3816, 8, 'project'); +INSERT INTO `pear_project_auth_node` VALUES (3817, 8, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (3818, 8, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3819, 8, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3820, 8, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (3821, 8, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3822, 8, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (3823, 8, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3824, 8, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3825, 8, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (3826, 8, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3827, 8, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (3828, 8, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (3829, 8, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3830, 8, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (3831, 8, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (3832, 8, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (3833, 8, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (3834, 8, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (3835, 8, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3836, 8, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3837, 8, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (3838, 8, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3839, 8, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3840, 8, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (3841, 8, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3842, 8, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3843, 8, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3844, 8, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (3845, 8, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3846, 8, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3847, 8, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3848, 8, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3849, 8, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3850, 8, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3851, 8, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3852, 8, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3853, 8, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3854, 8, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3855, 8, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3856, 8, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (3857, 8, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (3858, 8, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (3859, 8, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (3860, 8, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (3861, 8, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (3862, 8, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3863, 8, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3864, 8, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (3865, 8, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (3866, 8, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3867, 8, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3868, 8, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3869, 8, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3870, 8, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3871, 8, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3872, 8, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3873, 8, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3874, 8, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3875, 8, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3876, 8, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3877, 8, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3878, 8, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3879, 8, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (3880, 8, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3881, 8, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3882, 8, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3883, 8, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3884, 8, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3885, 8, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3886, 8, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3887, 8, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3888, 8, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (3889, 8, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (3890, 8, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (3891, 8, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3892, 8, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3893, 8, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (3894, 8, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3895, 8, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3896, 8, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3897, 8, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (3898, 8, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3899, 8, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3900, 8, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (3901, 8, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3902, 8, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3903, 8, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (3904, 8, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3905, 8, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3906, 8, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3907, 8, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3908, 8, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3909, 8, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3910, 8, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3911, 8, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3912, 8, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3913, 8, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3914, 8, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3915, 8, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (3916, 8, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (3917, 8, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3918, 8, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3919, 8, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3920, 8, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3921, 8, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (3922, 8, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3923, 8, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3924, 8, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3925, 8, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (3926, 8, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (3927, 8, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3928, 8, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3929, 8, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3930, 8, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (3931, 8, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3932, 8, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3933, 8, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (3934, 8, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3935, 8, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (3936, 8, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3937, 8, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3938, 9, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3939, 9, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3940, 9, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (3941, 9, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3942, 9, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (3943, 9, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (3944, 9, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (3945, 9, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (3946, 9, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (3947, 9, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (3948, 9, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (3949, 9, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (3950, 9, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (3951, 9, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (3952, 9, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (3953, 9, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (3954, 9, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (3955, 9, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (3956, 9, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (3957, 9, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (3958, 9, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3959, 9, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (3960, 9, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (3961, 9, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (3962, 9, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (3963, 9, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (3964, 9, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (3965, 9, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (3966, 9, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (3967, 9, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (3968, 9, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (3969, 9, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3970, 9, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3971, 9, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (3972, 9, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (3973, 9, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (3974, 9, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (3975, 9, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (3976, 9, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3977, 9, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (3978, 9, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (3979, 9, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (3980, 9, 'project/task_log/index'); +INSERT INTO `pear_project_auth_node` VALUES (3981, 9, 'project/task_log'); +INSERT INTO `pear_project_auth_node` VALUES (3982, 9, 'project/task_log/getlistbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (3983, 9, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3984, 9, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3985, 9, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (3986, 9, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (3987, 9, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (3988, 9, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (3989, 9, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3990, 9, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (3991, 9, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (3992, 9, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (3993, 9, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3994, 9, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (3995, 9, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (3996, 4, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (3997, 4, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (3998, 4, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (3999, 4, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (4000, 4, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4001, 4, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4002, 4, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (4003, 4, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4004, 4, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (4005, 4, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (4006, 4, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (4007, 4, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (4008, 4, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (4009, 4, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (4010, 4, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (4011, 4, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (4012, 4, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (4013, 4, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (4014, 4, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (4015, 4, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (4016, 4, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (4017, 4, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (4018, 4, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (4019, 4, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (4020, 4, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4021, 4, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (4022, 4, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (4023, 4, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (4024, 4, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (4025, 4, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4026, 4, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4027, 4, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4028, 4, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4029, 4, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (4030, 4, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (4031, 4, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (4032, 4, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (4033, 4, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4034, 4, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4035, 4, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (4036, 4, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4037, 4, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (4038, 4, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (4039, 4, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (4040, 4, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (4041, 4, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4042, 4, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (4043, 4, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (4044, 4, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (4045, 4, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4046, 4, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4047, 4, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (4048, 4, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (4049, 4, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4050, 4, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4051, 4, 'project/file/index'); +INSERT INTO `pear_project_auth_node` VALUES (4052, 4, 'project/file/read'); +INSERT INTO `pear_project_auth_node` VALUES (4053, 4, 'project/file/uploadfiles'); +INSERT INTO `pear_project_auth_node` VALUES (4054, 4, 'project/task/datetotalforproject'); +INSERT INTO `pear_project_auth_node` VALUES (4055, 4, 'project/project/getlogbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (4056, 4, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (4057, 3, 'project/account/index'); +INSERT INTO `pear_project_auth_node` VALUES (4058, 3, 'project/account'); +INSERT INTO `pear_project_auth_node` VALUES (4059, 3, 'project/account/auth'); +INSERT INTO `pear_project_auth_node` VALUES (4060, 3, 'project/account/add'); +INSERT INTO `pear_project_auth_node` VALUES (4061, 3, 'project/account/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4062, 3, 'project/account/del'); +INSERT INTO `pear_project_auth_node` VALUES (4063, 3, 'project/account/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (4064, 3, 'project/account/resume'); +INSERT INTO `pear_project_auth_node` VALUES (4065, 3, 'project/auth/index'); +INSERT INTO `pear_project_auth_node` VALUES (4066, 3, 'project/auth'); +INSERT INTO `pear_project_auth_node` VALUES (4067, 3, 'project/auth/apply'); +INSERT INTO `pear_project_auth_node` VALUES (4068, 3, 'project/auth/add'); +INSERT INTO `pear_project_auth_node` VALUES (4069, 3, 'project/auth/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4070, 3, 'project/auth/forbid'); +INSERT INTO `pear_project_auth_node` VALUES (4071, 3, 'project/auth/resume'); +INSERT INTO `pear_project_auth_node` VALUES (4072, 3, 'project/auth/setdefault'); +INSERT INTO `pear_project_auth_node` VALUES (4073, 3, 'project/auth/del'); +INSERT INTO `pear_project_auth_node` VALUES (4074, 3, 'project/department/index'); +INSERT INTO `pear_project_auth_node` VALUES (4075, 3, 'project/department'); +INSERT INTO `pear_project_auth_node` VALUES (4076, 3, 'project/department/read'); +INSERT INTO `pear_project_auth_node` VALUES (4077, 3, 'project/department/save'); +INSERT INTO `pear_project_auth_node` VALUES (4078, 3, 'project/department/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4079, 3, 'project/department/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4080, 3, 'project/department_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4081, 3, 'project/department_member'); +INSERT INTO `pear_project_auth_node` VALUES (4082, 3, 'project/department_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4083, 3, 'project/department_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4084, 3, 'project/department_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (4085, 3, 'project/index/index'); +INSERT INTO `pear_project_auth_node` VALUES (4086, 3, 'project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4087, 3, 'project/index/changecurrentorganization'); +INSERT INTO `pear_project_auth_node` VALUES (4088, 3, 'project/index/systemconfig'); +INSERT INTO `pear_project_auth_node` VALUES (4089, 3, 'project/index/info'); +INSERT INTO `pear_project_auth_node` VALUES (4090, 3, 'project/index/editpersonal'); +INSERT INTO `pear_project_auth_node` VALUES (4091, 3, 'project/index/editpassword'); +INSERT INTO `pear_project_auth_node` VALUES (4092, 3, 'project/index/uploadimg'); +INSERT INTO `pear_project_auth_node` VALUES (4093, 3, 'project/index/uploadavatar'); +INSERT INTO `pear_project_auth_node` VALUES (4094, 3, 'project/menu/menu'); +INSERT INTO `pear_project_auth_node` VALUES (4095, 3, 'project/menu'); +INSERT INTO `pear_project_auth_node` VALUES (4096, 3, 'project/menu/menuadd'); +INSERT INTO `pear_project_auth_node` VALUES (4097, 3, 'project/menu/menuedit'); +INSERT INTO `pear_project_auth_node` VALUES (4098, 3, 'project/menu/menuforbid'); +INSERT INTO `pear_project_auth_node` VALUES (4099, 3, 'project/menu/menuresume'); +INSERT INTO `pear_project_auth_node` VALUES (4100, 3, 'project/menu/menudel'); +INSERT INTO `pear_project_auth_node` VALUES (4101, 3, 'project/node/index'); +INSERT INTO `pear_project_auth_node` VALUES (4102, 3, 'project/node'); +INSERT INTO `pear_project_auth_node` VALUES (4103, 3, 'project/node/alllist'); +INSERT INTO `pear_project_auth_node` VALUES (4104, 3, 'project/node/clear'); +INSERT INTO `pear_project_auth_node` VALUES (4105, 3, 'project/node/save'); +INSERT INTO `pear_project_auth_node` VALUES (4106, 3, 'project/notify/index'); +INSERT INTO `pear_project_auth_node` VALUES (4107, 3, 'project/notify'); +INSERT INTO `pear_project_auth_node` VALUES (4108, 3, 'project/notify/noreads'); +INSERT INTO `pear_project_auth_node` VALUES (4109, 3, 'project/notify/setreadied'); +INSERT INTO `pear_project_auth_node` VALUES (4110, 3, 'project/notify/batchdel'); +INSERT INTO `pear_project_auth_node` VALUES (4111, 3, 'project/notify/read'); +INSERT INTO `pear_project_auth_node` VALUES (4112, 3, 'project/notify/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4113, 3, 'project/organization/index'); +INSERT INTO `pear_project_auth_node` VALUES (4114, 3, 'project/organization'); +INSERT INTO `pear_project_auth_node` VALUES (4115, 3, 'project/organization/save'); +INSERT INTO `pear_project_auth_node` VALUES (4116, 3, 'project/organization/read'); +INSERT INTO `pear_project_auth_node` VALUES (4117, 3, 'project/organization/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4118, 3, 'project/organization/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4119, 3, 'project/project/index'); +INSERT INTO `pear_project_auth_node` VALUES (4120, 3, 'project/project'); +INSERT INTO `pear_project_auth_node` VALUES (4121, 3, 'project/project/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4122, 3, 'project/project/save'); +INSERT INTO `pear_project_auth_node` VALUES (4123, 3, 'project/project/read'); +INSERT INTO `pear_project_auth_node` VALUES (4124, 3, 'project/project/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4125, 3, 'project/project/getlogbyselfproject'); +INSERT INTO `pear_project_auth_node` VALUES (4126, 3, 'project/project/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (4127, 3, 'project/project/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (4128, 3, 'project/project/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (4129, 3, 'project/project/archive'); +INSERT INTO `pear_project_auth_node` VALUES (4130, 3, 'project/project/recoveryarchive'); +INSERT INTO `pear_project_auth_node` VALUES (4131, 3, 'project/project/quit'); +INSERT INTO `pear_project_auth_node` VALUES (4132, 3, 'project/project_collect/collect'); +INSERT INTO `pear_project_auth_node` VALUES (4133, 3, 'project/project_collect'); +INSERT INTO `pear_project_auth_node` VALUES (4134, 3, 'project/project_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4135, 3, 'project/project_member'); +INSERT INTO `pear_project_auth_node` VALUES (4136, 3, 'project/project_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4137, 3, 'project/project_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4138, 3, 'project/project_member/removemember'); +INSERT INTO `pear_project_auth_node` VALUES (4139, 3, 'project/project_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4140, 3, 'project/project_template'); +INSERT INTO `pear_project_auth_node` VALUES (4141, 3, 'project/project_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (4142, 3, 'project/project_template/uploadcover'); +INSERT INTO `pear_project_auth_node` VALUES (4143, 3, 'project/project_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4144, 3, 'project/project_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4145, 3, 'project/task/index'); +INSERT INTO `pear_project_auth_node` VALUES (4146, 3, 'project/task'); +INSERT INTO `pear_project_auth_node` VALUES (4147, 3, 'project/task/datetotalforproject'); +INSERT INTO `pear_project_auth_node` VALUES (4148, 3, 'project/task/selflist'); +INSERT INTO `pear_project_auth_node` VALUES (4149, 3, 'project/task/tasksources'); +INSERT INTO `pear_project_auth_node` VALUES (4150, 3, 'project/task/read'); +INSERT INTO `pear_project_auth_node` VALUES (4151, 3, 'project/task/save'); +INSERT INTO `pear_project_auth_node` VALUES (4152, 3, 'project/task/taskdone'); +INSERT INTO `pear_project_auth_node` VALUES (4153, 3, 'project/task/assigntask'); +INSERT INTO `pear_project_auth_node` VALUES (4154, 3, 'project/task/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4155, 3, 'project/task/createcomment'); +INSERT INTO `pear_project_auth_node` VALUES (4156, 3, 'project/task/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4157, 3, 'project/task/like'); +INSERT INTO `pear_project_auth_node` VALUES (4158, 3, 'project/task/star'); +INSERT INTO `pear_project_auth_node` VALUES (4159, 3, 'project/task/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (4160, 3, 'project/task/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (4161, 3, 'project/task/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4162, 3, 'project/task_member/index'); +INSERT INTO `pear_project_auth_node` VALUES (4163, 3, 'project/task_member'); +INSERT INTO `pear_project_auth_node` VALUES (4164, 3, 'project/task_member/searchinvitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4165, 3, 'project/task_member/invitemember'); +INSERT INTO `pear_project_auth_node` VALUES (4166, 3, 'project/task_member/invitememberbatch'); +INSERT INTO `pear_project_auth_node` VALUES (4167, 3, 'project/task_stages/index'); +INSERT INTO `pear_project_auth_node` VALUES (4168, 3, 'project/task_stages'); +INSERT INTO `pear_project_auth_node` VALUES (4169, 3, 'project/task_stages/tasks'); +INSERT INTO `pear_project_auth_node` VALUES (4170, 3, 'project/task_stages/sort'); +INSERT INTO `pear_project_auth_node` VALUES (4171, 3, 'project/task_stages/save'); +INSERT INTO `pear_project_auth_node` VALUES (4172, 3, 'project/task_stages/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4173, 3, 'project/task_stages/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4174, 3, 'project/task_stages_template/index'); +INSERT INTO `pear_project_auth_node` VALUES (4175, 3, 'project/task_stages_template'); +INSERT INTO `pear_project_auth_node` VALUES (4176, 3, 'project/task_stages_template/save'); +INSERT INTO `pear_project_auth_node` VALUES (4177, 3, 'project/task_stages_template/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4178, 3, 'project/task_stages_template/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4179, 3, 'project/source_link'); +INSERT INTO `pear_project_auth_node` VALUES (4180, 3, 'project/source_link/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4181, 3, 'project/task/tasklog'); +INSERT INTO `pear_project_auth_node` VALUES (4182, 3, 'project/task/recyclebatch'); +INSERT INTO `pear_project_auth_node` VALUES (4183, 3, 'project/file'); +INSERT INTO `pear_project_auth_node` VALUES (4184, 3, 'project/file/index'); +INSERT INTO `pear_project_auth_node` VALUES (4185, 3, 'project/file/read'); +INSERT INTO `pear_project_auth_node` VALUES (4186, 3, 'project/file/uploadfiles'); +INSERT INTO `pear_project_auth_node` VALUES (4187, 3, 'project/file/edit'); +INSERT INTO `pear_project_auth_node` VALUES (4188, 3, 'project/file/recycle'); +INSERT INTO `pear_project_auth_node` VALUES (4189, 3, 'project/file/recovery'); +INSERT INTO `pear_project_auth_node` VALUES (4190, 3, 'project/file/delete'); +INSERT INTO `pear_project_auth_node` VALUES (4191, 3, 'project'); + +-- ---------------------------- +-- Table structure for pear_project_collection +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_collection`; +CREATE TABLE `pear_project_collection` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '项目id', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加入时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 36 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目-收藏表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_collection +-- ---------------------------- +INSERT INTO `pear_project_collection` VALUES (29, 'n5opgqevrz1l03h48uwx67d2', '6v7be19pwman2fird04gqu53', '2019-01-02 17:40:18'); +INSERT INTO `pear_project_collection` VALUES (30, '8ulzfth64cd0k1x5peivowm2', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 09:16:15'); +INSERT INTO `pear_project_collection` VALUES (35, 'elqa703jyvfhpt1dsxkzi8on', '6v7be19pwman2fird04gqu53', '2019-01-04 21:44:19'); + +-- ---------------------------- +-- Table structure for pear_project_config +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_config`; +CREATE TABLE `pear_project_config` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `project_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '项目配置表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for pear_project_log +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_log`; +CREATE TABLE `pear_project_log` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '操作人id', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '操作内容', + `remark` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL, + `type` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'create' COMMENT '操作类型', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '添加时间', + `source_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务id', + `action_type` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '场景类型', + `to_member_code` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0', + `is_comment` tinyint(1) NULL DEFAULT 0 COMMENT '是否评论,0:否', + `project_code` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `icon` varchar(20) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4334 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目日志表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_log +-- ---------------------------- +INSERT INTO `pear_project_log` VALUES (3865, 'hb8l9ca23fv6konryzetwid5', '6v7be19pwman2fird04gqu53', '', '重做了任务', 'redo', '2018-12-29 10:15:50', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3866, '7x0cmb8jezoihy2nrspqdav6', '6v7be19pwman2fird04gqu53', '', '完成了任务', 'done', '2018-12-29 10:15:55', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3867, 'l5pem3zy2ts0oa87rf64cv9u', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了内容', 'name', '2018-12-29 10:16:10', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3868, 'va9og4r2e076puhw8d3k51xl', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了内容', 'name', '2018-12-29 10:16:10', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3869, 'u8yzk57ndfpj60rwes4b1la3', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了内容', 'name', '2018-12-29 10:16:56', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3870, '4ys53x2eoaimgpdrhuzk9ncw', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了备注', 'content', '2018-12-29 10:17:20', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (3871, '3jnmaiwh2k0gse5z6ycb9qul', '6v7be19pwman2fird04gqu53', '', '更新截止时间为DecDec月SunSun日 2222:1212', 'setEndTime', '2018-12-29 10:21:25', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3872, 'yxdhomi1jzu38plq42v0gar7', '6v7be19pwman2fird04gqu53', '', '更新截止时间为12月30日 22:04', 'setEndTime', '2018-12-29 10:23:47', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3873, '9vnih5ysb6aj2gd1rpu47xzk', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 22:04', 'setEndTime', '2018-12-29 10:24:26', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3874, 'b7qloh6psyzevmr84fcd3n9x', '6v7be19pwman2fird04gqu53', '', '清除了截止时间 ', 'clearEndTime', '2018-12-29 10:25:41', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3875, 'j3oiehapycrg16v8km9zdq2f', '6v7be19pwman2fird04gqu53', '', '移除了执行者 vilson.2', 'removeExecutor', '2018-12-29 10:49:20', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3876, 'azp3jxlsk04w9vfdqg1ho27y', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson', 'assign', '2018-12-29 10:51:06', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3877, 'crjuwktfns89d2l6ypog3qbm', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 10:52:00', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3878, '3pbxrfhc5nyil20gzjdweva6', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 10:52:06', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3879, 'bal3tv27mjqn1riusfywokcz', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson.2', 'removeMember', '2018-12-29 10:52:12', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3880, 't4o85dv2mi6qr9yn7e30asbh', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson.2', 'inviteMember', '2018-12-29 10:52:26', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (3881, '0gd32z4bwmexs7lthurfynqv', '6v7be19pwman2fird04gqu53', '', '移除了执行者 vilson', 'removeExecutor', '2018-12-29 10:52:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3882, '6bzg8p1u5f0j9dintrhoak37', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson', 'removeMember', '2018-12-29 10:52:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3883, '7z0pdbgmkfqnhyc9wsrau2vx', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson.2', 'removeMember', '2018-12-29 10:52:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3884, '0ca81iu4d5m9zj3o6ekvw7sq', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson', 'inviteMember', '2018-12-29 10:53:23', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (3885, '6w0ylbu1si5pg9mtjovqhrnd', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson.2', 'inviteMember', '2018-12-29 10:53:23', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (3886, 'c6s7evftijkbal5huo8mqrwd', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson', 'removeMember', '2018-12-29 10:53:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3887, '7c6qswjvx95rne8dy10h2bli', '6v7be19pwman2fird04gqu53', '', '移除了参与者 vilson.2', 'removeMember', '2018-12-29 10:53:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3888, 'gs2ykol8h95af76mczx04jbd', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 10:57:21', 'hj5s73zk6amd9wfvbxoygpic', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3889, '54pznxmvueifhrok3lbj17a9', '6v7be19pwman2fird04gqu53', '', '完成了子任务 ', 'doneChild', '2018-12-29 10:57:21', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3890, '5uqkjha30czp4xltbv12dmwy', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 10:57:25', '4mtnhwbe0gjdkaur2ic7xsv6', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3891, 'dtbn6e3kiz42aly05qjuf9v8', '6v7be19pwman2fird04gqu53', '', '重做了子任务 ', 'redoChild', '2018-12-29 10:57:25', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3892, 'tpeohjivxzds8n7mwk4ba6yg', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 10:57:31', 'g83vs5t47dfnprchqzel1a29', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3893, 'n82qfxou094y5vg76rdcsbph', '6v7be19pwman2fird04gqu53', '', '完成了子任务 ', 'doneChild', '2018-12-29 10:57:31', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3894, 'waog819ziun43ql5ptf7hs2r', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 10:58:47', 'hj5s73zk6amd9wfvbxoygpic', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3895, 'imy4zkq7v8ehb02nl9uos16x', '6v7be19pwman2fird04gqu53', '', '重做了子任务 \'正在测试66\'', 'redoChild', '2018-12-29 10:58:47', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3896, 'r1hsabj6xm74p5qvf0ltcw92', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 10:59:38', '0zvn3ug6fiqhdpkljos79xaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3897, '0k6zht3acignxrs785b9pewl', '6v7be19pwman2fird04gqu53', '', '重做了子任务 “正在测试66”', 'redoChild', '2018-12-29 10:59:38', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3898, '967lzeit5d4an3yxbwhpomqg', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 11:00:21', '6kqh4b1mce05rzvuljsg3ow8', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3899, 'eizs4oy06gdkb9rvu1x2q7jc', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"正在测试66\"', 'doneChild', '2018-12-29 11:00:21', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3900, 'src7od6qp4age5umt3yb1kf2', '6v7be19pwman2fird04gqu53', '12', '创建了任务 ', 'create', '2018-12-29 11:02:39', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3901, 'uiyh86tmclzv75pwojefdrq1', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 11:02:39', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3902, 'qj59cy1iom4axe8znvhgfl2k', '6v7be19pwman2fird04gqu53', '88', '创建了任务 ', 'create', '2018-12-29 11:02:49', 'v2kr731dihezctslmx9agb5w', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3903, 'ngcptv1mkb08sde5uorfa4zw', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2018-12-29 11:02:49', 'v2kr731dihezctslmx9agb5w', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3904, 'rjyt3nkd6bs4e9owf2qv0xpu', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 11:03:30', 'v2kr731dihezctslmx9agb5w', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3905, '6du4nlszheact9v7rpqjkbgw', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 11:16:37', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (3906, 'e6n89tqal4pkf3c02xo1i7vh', '6v7be19pwman2fird04gqu53', '正在测试66', '更新了备注 ', 'content', '2018-12-29 12:09:11', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (3907, 'dgt56ixya7bm9ljp8cr2wsh4', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 12:10:01', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (3908, '9uxe30yni8lhpbdcm2t74fjq', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2018-12-29 12:10:03', 'edbn6rz89fmh7a3ilgvukw14', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (3909, 'xdnbuiyl36etvzwhp9f5o07g', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月29日 18:00', 'setEndTime', '2018-12-29 12:10:06', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (3910, 'zjirf8e054oscx3mlu2b9gp1', '6v7be19pwman2fird04gqu53', '22', '更新了备注 ', 'content', '2018-12-29 12:10:10', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (3911, 'bjsgzv69hap8xoc4i5umq1re', '6v7be19pwman2fird04gqu53', '22', '更新了内容 ', 'name', '2018-12-29 12:10:11', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (3912, 'zk6vu1pin4jy2f9w0gc5b73m', '6v7be19pwman2fird04gqu53', '88', '创建了任务 ', 'create', '2018-12-29 12:10:16', '1vqt5zg4shbkum2dc8jxow7f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3913, 'qw03pol295t1diz846erycsj', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:10:16', '1vqt5zg4shbkum2dc8jxow7f', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3914, 'bml3dzxq6a07tgv9k1u8nwe2', '6v7be19pwman2fird04gqu53', '99', '创建了任务 ', 'create', '2018-12-29 12:11:40', '2hg0yfa9jpxz6q58ckrdol1u', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3915, 'oynufrwkag4lvqc7pbszm62d', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"22\"', 'createChild', '2018-12-29 12:11:40', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3916, 't69wv1zanypbxj2srg34ifkm', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:11:40', '2hg0yfa9jpxz6q58ckrdol1u', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3917, '7k6rt1vmhcdb89jxawsiz23n', '6v7be19pwman2fird04gqu53', '77', '创建了任务 ', 'create', '2018-12-29 12:11:57', 'bi5ajpmxfsk9rwdg4l32yv1n', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3918, 'bs85197y2hl4gzaowp6ujxfc', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"22\"', 'createChild', '2018-12-29 12:11:57', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3919, 'klijc6v9hy8dx457mfp3otra', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:11:57', 'bi5ajpmxfsk9rwdg4l32yv1n', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3920, 'h72aki9vsqzf0lrwb18n6yd3', '6v7be19pwman2fird04gqu53', '12', '创建了任务 ', 'create', '2018-12-29 12:12:32', 'dmbtgy3phi2sz7j89q10xcoa', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3921, '97t5lacuwmre4v8jsh0g3q2p', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"22\"', 'createChild', '2018-12-29 12:12:32', 'edbn6rz89fmh7a3ilgvukw14', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3922, '9dok2m0rjq81zsbptc45x7yf', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 12:12:32', 'dmbtgy3phi2sz7j89q10xcoa', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3923, 'jhmi9ud8nxv7t26bzf5wspc0', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:12:41', 'ywiahqcb50rkem376js9nflv', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3924, 'vswa2btoujq5ycmfz4193npk', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:12:41', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3925, 'm8195lgdibutzjrqo23aeh06', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:12:41', 'ywiahqcb50rkem376js9nflv', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3926, 'ylz9erqbjn3d0hxv8go6fitm', '6v7be19pwman2fird04gqu53', '2', '创建了任务 ', 'create', '2018-12-29 12:12:42', 'xebf08p2uc9sj6mkr5714lat', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3927, 'oy0f23w568vg9jlqapebmdns', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:12:42', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3928, 'xsfe94pvt87mbyi36oqrk10c', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:12:42', 'xebf08p2uc9sj6mkr5714lat', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3929, 'hxajk4rplqoe9gn1tu8672fb', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-29 12:12:44', 'p5axvq94lr7enmhb83o2zfks', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3930, 'zmb27c59vxne6jar3if04hlk', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:12:44', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3931, 'v0t2yzoch1a6urex3lb8npfw', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:12:44', 'p5axvq94lr7enmhb83o2zfks', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3932, '2kvg64szutj5amh89d1q0i7w', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-29 12:14:21', 'lert5uyi790q2jfpbmzgak1o', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3933, 'b8l3jgk10a6h5mq2pyz9eoiv', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:14:21', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3934, 'pe7mrlxw2iqoyg6ka3bcfszh', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:14:21', 'lert5uyi790q2jfpbmzgak1o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3935, 'ouci27hjl9f56mxekwypdv3g', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:14:30', '25rzhoykix6pajsw9cgm1408', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3936, '2pma6dyvwj9ktrbe0u58igzl', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"789\"', 'createChild', '2018-12-29 12:14:30', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3937, 'bj6hlw48vcmori9es0pzfdun', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:14:30', '25rzhoykix6pajsw9cgm1408', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3938, '06sw7kf1ricaqg2yoxnd8buj', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:15:33', 'cmrdn8soz4g26fy0qa3ib7te', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3939, 'pu8jn0labrgs3w7qxoh2i1ct', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"333\"', 'createChild', '2018-12-29 12:15:33', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3940, 'jsyp6c5a1elmidvnk89g472r', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:15:33', 'cmrdn8soz4g26fy0qa3ib7te', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3942, 'wkdzbvgh6stn7r1i4yxpoj9q', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-29 12:16:19', 'qfakryig3ztpv5uw6e2obx08', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3943, 'e3z5lfu497no06hqrwdmcxgb', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"3\"', 'createChild', '2018-12-29 12:16:19', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3944, 'g8d693u0qopr5msa2itvhejc', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:16:19', 'qfakryig3ztpv5uw6e2obx08', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3945, 'thw032pbsjf8q61yoadk94eg', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-29 12:22:17', 'e16tvkg4uoixnz9hjaf5l3wr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3946, 'km0t9szd1gc37alpujqhf8ry', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"4\"', 'createChild', '2018-12-29 12:22:17', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3947, 'witaxj3zvcf58rbq0y6s97nm', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:17', 'e16tvkg4uoixnz9hjaf5l3wr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3948, 'l1u2ncj9deq6i5bp8tysrg3z', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:22:18', 'f35oyrln286s7p4u9vmeghdw', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3949, 'efgjtyv1d3uckbos62xa078h', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"5\"', 'createChild', '2018-12-29 12:22:18', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3950, 'w1n6gshr5ftk3b0pjvqxyld2', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:18', 'f35oyrln286s7p4u9vmeghdw', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3951, '6wfip04jx5h8nvdzes29gb1y', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-29 12:22:19', 'yqwe2aiupvg5zj1so4krm86l', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3952, 'rt7jwfvlsi0hbazod9mc2x6n', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"6\"', 'createChild', '2018-12-29 12:22:19', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3953, '3y5rmwga8dfi9npulbv2c01q', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:19', 'yqwe2aiupvg5zj1so4krm86l', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3954, 'jnlt9q7udp86rme0h4vogk3s', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:22:22', 'yhs4xb7g3vu9pnwfq5l1zioa', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3955, 'd5ji698spacgwzq7n2tky3ru', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:22:22', 'tx6loaugrd0s3e1mhk52iznp', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3956, '4b90ze26hmprkw5adxjy8ivt', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:22', 'yhs4xb7g3vu9pnwfq5l1zioa', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3957, '5f0xugvdj3wp46hyte7lnamk', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:22:52', 'jo3vbswk8ze57filmcptun2g', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3958, '7bmtyslzh0w8aeu213onq69p', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"5\"', 'createChild', '2018-12-29 12:22:52', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3959, 'rwy5p9fnkbc0ds4ltoeiz7am', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:52', 'jo3vbswk8ze57filmcptun2g', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3960, 'paxcuosjmf7g831zl0ndvhbe', '6v7be19pwman2fird04gqu53', '8', '创建了任务 ', 'create', '2018-12-29 12:22:53', '35i8ptd1fs9hrbk0glv47ycj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3961, 'mh0fe4tr9iup72jn85z6xay3', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"8\"', 'createChild', '2018-12-29 12:22:53', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3962, '6jurq17m9fl43ekcn5x8y2oa', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:53', '35i8ptd1fs9hrbk0glv47ycj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3963, '7o12ivmp9ygu5kjclzn6rwd3', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:22:54', 'pn3dmjavhrc1bewgi4yz25x9', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3964, 'k7dht9cq4e01vns2ojxgiyb8', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:22:54', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3965, 'cl5s4ro3m1x9hejf8dbyzatk', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:54', 'pn3dmjavhrc1bewgi4yz25x9', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3966, '54taxf2qnbymd9wiczo0rpve', '6v7be19pwman2fird04gqu53', '9', '创建了任务 ', 'create', '2018-12-29 12:22:55', 'p5r8a4gwfxmn9sqit2jvkl71', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3967, 'y10gb254fwrimqnu9jszkc73', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"9\"', 'createChild', '2018-12-29 12:22:55', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3968, 'j8m1lxeschgiw6ot95n4r2av', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:22:55', 'p5r8a4gwfxmn9sqit2jvkl71', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3969, 'xvlo3je8y61trsi0wdm7ac45', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:23:00', 'aenxk82rgwm5qcsuo0b7hi4f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3970, '3ynx5bf61qmev4rjl2w8gksc', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"1\"', 'createChild', '2018-12-29 12:23:00', 'oz2xwp8v0niahdc7lekjtsrg', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3971, 'hkpx0i56wftbj7o2c84agl1m', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:23:00', 'aenxk82rgwm5qcsuo0b7hi4f', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3972, 'cuhw96e2mk5ft0rpalgb8doq', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:24:25', 'vf8xtpclba3od21kmih5us9q', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3973, 'fm9oyz65ajn0xqb81wc3ievr', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:24:25', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3974, '952evikzqod30sh6a41mbgru', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:24:25', 'vf8xtpclba3od21kmih5us9q', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3975, 'l4merydg0x3jkbiuowqc28fs', '6v7be19pwman2fird04gqu53', '9', '创建了任务 ', 'create', '2018-12-29 12:24:26', '2ohuv1jm3abrs8ldni5p9z06', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3976, 'u1d53a8ecxvnl9zpth0qgw4j', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"9\"', 'createChild', '2018-12-29 12:24:26', '3urs09e57btygqhjdfx2pwmn', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3977, 'wtc2u0jsx9h4o8vz1p376mrl', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:24:26', '2ohuv1jm3abrs8ldni5p9z06', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3978, '0adg97cq2ipl5bmvj13rkt4w', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-29 12:29:39', 'wamzl34n6jfrhiyoe09v2ktb', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3979, 'cjf17vsmiz84raxk52eunwdh', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"1\"', 'createChild', '2018-12-29 12:29:39', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3980, 'buw4ozsyxakdit6e5vf3l7q9', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:39', 'wamzl34n6jfrhiyoe09v2ktb', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3981, '4osxw90np216glrhumzk7fcj', '6v7be19pwman2fird04gqu53', '2', '创建了任务 ', 'create', '2018-12-29 12:29:40', 'rgshi61c4xol5ue9f2n8m3bd', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3982, 'e0u7act8qihwdm3nor9bxv5z', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"2\"', 'createChild', '2018-12-29 12:29:40', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3983, 'pi7y3njsxumgchbf6r2vozqd', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:40', 'rgshi61c4xol5ue9f2n8m3bd', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3984, 'cbx4gq9voi51dz06lfykhp2w', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-29 12:29:41', 'kny3xf7o41rli9s0pwjmhdqc', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3985, 'mt8qukh2w6xdypoger7zis54', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"3\"', 'createChild', '2018-12-29 12:29:41', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3986, 'wy2ch8xs7ftmk93rlnivbeu4', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:41', 'kny3xf7o41rli9s0pwjmhdqc', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3987, 'dmike23ryw5qhf0anxpt46bj', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-29 12:29:42', 'y2zos3hg9058alwet46xmukp', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3988, '9juosez4wvklpdchq37826nt', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"4\"', 'createChild', '2018-12-29 12:29:42', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3989, 'fpjz6s58owxd20b3irhkynm1', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:42', 'y2zos3hg9058alwet46xmukp', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3990, 'xmudezpvr4613atijfqlwhyn', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-29 12:29:43', 'wqu7verc5i4p19h0y32x6tmo', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3991, 'upa9vqkys30fmejcz4nh1rwd', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"5\"', 'createChild', '2018-12-29 12:29:43', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3992, 'uwvdnqm1y893irt5jc46sze0', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:43', 'wqu7verc5i4p19h0y32x6tmo', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3993, 'egms06q7hnw4ulf3p1jvotkb', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-29 12:29:44', '0h8mt7sw1ki5fuxv2y6d3jag', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3994, 'ax842wv1uenslbizyth7om0p', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"6\"', 'createChild', '2018-12-29 12:29:44', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3995, '85hnga9li4mfqv1weo6tyzds', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:44', '0h8mt7sw1ki5fuxv2y6d3jag', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3996, 's0oqyw9z1ukhmd7ce2xpf5v4', '6v7be19pwman2fird04gqu53', '7', '创建了任务 ', 'create', '2018-12-29 12:29:46', '1hcrmvgfjtu5qnadl869bkxo', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (3997, 'c4hdo7sxeqam9gt5runbl0py', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"7\"', 'createChild', '2018-12-29 12:29:46', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (3998, 'udfj9135zixo7g2rway6mknc', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:46', '1hcrmvgfjtu5qnadl869bkxo', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (3999, 'k6h170ma4zteuyb9dp8nicsf', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-29 12:29:54', 'puj84l5av3f0e7k9oyw2zqcr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4000, 'gk7bdt2pmilwc8hunx19y6zq', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"6\"', 'createChild', '2018-12-29 12:29:54', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4001, 'adhr0wxonpt27zk9ecq4m6u1', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:54', 'puj84l5av3f0e7k9oyw2zqcr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4002, 'se7w26u1xzk8cbm4oqfn3rpy', '6v7be19pwman2fird04gqu53', '8', '创建了任务 ', 'create', '2018-12-29 12:29:56', 'jftz4y5is1hwrlmac07nd8q6', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4003, '0m3hn9v7x2q4ig6etzwfo8kd', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"8\"', 'createChild', '2018-12-29 12:29:56', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4004, 'm7azwxsht603859gykcdpo2v', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 12:29:56', 'jftz4y5is1hwrlmac07nd8q6', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4005, 'jc7a30ur8bzgsvw29lxyinok', '6v7be19pwman2fird04gqu53', '415', NULL, 'comment', '2018-12-29 12:40:02', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4006, 'za961k07mbpd34vuoxfn8wc5', '6v7be19pwman2fird04gqu53', '8989', NULL, 'comment', '2018-12-29 12:40:21', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4007, 'j0k1h37iuqnocylw6fe94za2', '6v7be19pwman2fird04gqu53', '88ss', '更新了内容 ', 'name', '2018-12-29 12:47:25', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4008, 'v1e4dhlq7giuwk2bmz35ncf8', '6v7be19pwman2fird04gqu53', '88ss', '更新了备注 ', 'content', '2018-12-29 12:47:35', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4009, 'sdjkuz4bniprcha3qy6ow51m', '6v7be19pwman2fird04gqu53', '88ss', '更新了备注 ', 'content', '2018-12-29 12:47:41', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4010, 'rdnplfaxjvsuy8q39504eb7c', '6v7be19pwman2fird04gqu53', '88ss', '更新了备注 ', 'content', '2018-12-29 12:47:46', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4011, 'f4vq6utlabw7my1gnzx8390k', '6v7be19pwman2fird04gqu53', '99', NULL, 'comment', '2018-12-29 12:52:47', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4012, '5ope0jf6ytgqvl3m8xdh7bkz', '6v7be19pwman2fird04gqu53', 'dff', NULL, 'comment', '2018-12-29 12:58:22', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4013, 'd3b0xn69ljvpikcf2az4eg81', '6v7be19pwman2fird04gqu53', '8989', NULL, 'comment', '2018-12-29 12:58:24', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4014, 'aum2zlh4y38cj9t1nriebp07', '6v7be19pwman2fird04gqu53', '13\n55\n', NULL, 'comment', '2018-12-29 12:58:28', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4015, 'elwir0n5yzh4tbx1om87s3gk', '6v7be19pwman2fird04gqu53', '89\n\n\n66\n\n66\n', NULL, 'comment', '2018-12-29 12:59:23', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4016, 'tkxr8zwagy106f3ipmed7boj', '6v7be19pwman2fird04gqu53', 'sss
ee', NULL, 'comment', '2018-12-29 13:00:39', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4017, '5zdypx9jarfk836onmq2ibge', '6v7be19pwman2fird04gqu53', '9', NULL, 'comment', '2018-12-29 13:01:48', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4018, 'wh0v2fqp9nkix1sg56z8mljo', '6v7be19pwman2fird04gqu53', '测试', NULL, 'comment', '2018-12-29 13:01:52', '98lqy4vaiprk60uzbojdmsgx', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4019, '3xwke78sl49bragtovjhfp20', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 13:03:57', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4020, 'sdhi9e6kugrj2bnc83qt7y4w', '6v7be19pwman2fird04gqu53', '10', '创建了任务 ', 'create', '2018-12-29 13:05:03', '4fua38vpqgk706csx2lb9etj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4021, '6a14fuo902x73zqsrkvnywhg', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"10\"', 'createChild', '2018-12-29 13:05:03', '98lqy4vaiprk60uzbojdmsgx', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4022, 'fn3bho7stwlm92r6g4j1a5pk', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 13:05:03', '4fua38vpqgk706csx2lb9etj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4023, 'o7v31qb6ysi4fugat25chxmd', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:09:27', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4024, 'eycx8z7s3juo4git9pradqvh', '6v7be19pwman2fird04gqu53', '

这里是备注内容

', '更新了备注 ', 'content', '2018-12-29 13:10:28', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4025, 'q9hsz2valnwy0rfg8umpe3b7', '6v7be19pwman2fird04gqu53', '

这里是备注内容

', '更新了备注 ', 'content', '2018-12-29 13:10:58', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4026, 'zgonswqe0xvky469d2tfm3hj', '6v7be19pwman2fird04gqu53', 'dd', NULL, 'comment', '2018-12-29 13:16:32', 'aut9wrz1pn0elf5s47ivx26o', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4027, 'pjntgqukcr041hazbsv29imd', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:40', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4028, 'cqigul6pm3y4vrnabf2xowt9', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:42', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4029, 'jv7s1q0mw2hxdgfpk8r5zbuc', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:50', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4030, '5fgsnwq7xb2luvm6edz43yih', '6v7be19pwman2fird04gqu53', '正在测试', '更新了内容 ', 'name', '2018-12-29 13:18:58', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4031, '7f3485jcnsz69a0dwtgrbvlu', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 13:19:29', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4032, 'lagx0bjhvmdt61ufrqi37zw8', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 非常紧急', 'pri', '2018-12-29 13:21:38', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4033, 'qfyrd27bjm93otwkne58p4ia', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 普通', 'pri', '2018-12-29 13:21:42', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4034, '576y0p2s8zkvqejcfga341ud', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 非常紧急', 'pri', '2018-12-29 13:21:46', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4035, 'lmn8pc5r0j39zvq26wsatoxf', '6v7be19pwman2fird04gqu53', '

这里是备注内容


', '更新了备注 ', 'content', '2018-12-29 13:25:21', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4036, 'qibmkuh0pfnatdcs4j2xyw1o', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson.2', 'inviteMember', '2018-12-29 13:25:31', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4037, 'wc7u9qmy628okg0zpt1iejb3', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2018-12-29 13:25:36', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4038, 'l0z6a8u1rcjkxnte3pwobfv5', '6v7be19pwman2fird04gqu53', 'Tree 的 @on-select-change 和 @on-check-change 事件返回参数新增当前项', '更新了内容 ', 'name', '2018-12-29 13:27:16', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4039, 'x8bgefzjv0p4iqhtlmwad7s3', '6v7be19pwman2fird04gqu53', '修复 Table 动态设置表头分组报错的问题', '更新了内容 ', 'name', '2018-12-29 13:27:35', '4mtnhwbe0gjdkaur2ic7xsv6', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4040, 'hlm2ei7403a9g1byzdvrkowc', '6v7be19pwman2fird04gqu53', '新增阿拉伯语', '更新了内容 ', 'name', '2018-12-29 13:27:51', 'hj5s73zk6amd9wfvbxoygpic', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4041, 'ctm4v1hlno20ksafdb8qjew9', '6v7be19pwman2fird04gqu53', 'Table 支持 slot-scope 用法', '更新了内容 ', 'name', '2018-12-29 13:28:02', 'l027b1dyrv93zu4ewmtoa6q5', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4042, 'e95h10orj3pu78lvbtdmczsk', '6v7be19pwman2fird04gqu53', 'Table 新增取消全选事件 @on-select-all-cancel。', '更新了内容 ', 'name', '2018-12-29 13:28:24', 'rqb7vi254tna3uzhdgo0f6ey', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4043, '5v8hz7qt2kgdjwaf46ui3nrl', '6v7be19pwman2fird04gqu53', 'Table 新增取消全选事件 @on-select-all-cancel', '更新了内容 ', 'name', '2018-12-29 13:28:26', 'rqb7vi254tna3uzhdgo0f6ey', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4044, 'pm524anfjqdvl1ukwr3gzhyx', '6v7be19pwman2fird04gqu53', '123', '创建了任务 ', 'create', '2018-12-29 16:08:33', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4045, 'qlyenmbjf264ochakvu39r0w', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-29 16:08:33', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4046, '4eagjuoc15v3f6z0brkdsxwm', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-29 21:44:15', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4047, 'b7z84xwlmr3cyjt15p9dqhoi', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-29 21:45:14', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4048, '9krvs6f5j03hyxoduae4n7gm', '6v7be19pwman2fird04gqu53', '

这里是备注内容


', '更新了备注 ', 'content', '2018-12-29 21:45:42', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4049, 'scxy36o5gpkltiujn1rm0v79', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson', 'inviteMember', '2018-12-29 21:47:17', 'aut9wrz1pn0elf5s47ivx26o', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4050, '24x68z9pivtjlqabsdcg3ywk', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-29 21:47:20', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4051, '21ycsgt58k9urfql4nep7bda', '6v7be19pwman2fird04gqu53', 'Hi~', NULL, 'comment', '2018-12-29 21:47:28', 'aut9wrz1pn0elf5s47ivx26o', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4052, '4m9cj3eg6srqou2i5abxlwzh', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 09:04:09', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4053, 'f5x7v3sqhi1m4joy6cdbpu9t', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 10:22:49', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4054, '84bnae6csizdo3vph9kjw1q0', '6v7be19pwman2fird04gqu53', '123', '创建了任务 ', 'create', '2018-12-30 10:24:40', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4055, '1hyxiz4kt7j0pu9fs8enl5g6', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"123\"', 'createChild', '2018-12-30 10:24:40', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4056, 'djzr3kv7wicolasp2hmx96ub', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 10:24:40', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4057, 'vf8t0o5sp36hgazmijn1uewc', '6v7be19pwman2fird04gqu53', '222', '创建了任务 ', 'create', '2018-12-30 10:24:42', 'wrjgk84t2beam0yvxs61qinu', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4058, 'zaf14de3jrpobvins2kxm86u', '6v7be19pwman2fird04gqu53', '', '添加了子任务 \"222\"', 'createChild', '2018-12-30 10:24:42', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4059, 'agweirnvb41plzsfkx8jyt9c', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 10:24:42', 'wrjgk84t2beam0yvxs61qinu', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4060, 'usiaxdk1538bcty0mlgjrqoh', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-30 10:35:33', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4061, '5nyzc7djiefg80xtkqa32mpr', '6v7be19pwman2fird04gqu53', '', '完成了子任务 \"123\"', 'doneChild', '2018-12-30 10:35:33', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4062, '0zj738s1fit5blokvwgrmpy2', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-30 10:35:35', 'w80m92aopfbcru6s5qe7z3ti', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4063, 'heyz2qimsw6un7txjc8pklvg', '6v7be19pwman2fird04gqu53', '', '重做了子任务 \"123\"', 'redoChild', '2018-12-30 10:35:35', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4064, 'fgwdb84p9oazs6xk3cmynlj7', '6v7be19pwman2fird04gqu53', '789', NULL, 'comment', '2018-12-30 10:38:05', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4065, 'xioz6hrkbtelsmfw4cgyv983', '6v7be19pwman2fird04gqu53', '', '恢复了任务 ', 'recovery', '2018-12-30 11:22:14', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4066, 'l7ncz0t8fxj4sam5u6whq39p', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 11:23:39', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4067, '32hzsjagcwdr0817f9o6i5vu', '6v7be19pwman2fird04gqu53', '', '恢复了任务 ', 'recovery', '2018-12-30 11:23:43', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'undo'); +INSERT INTO `pear_project_log` VALUES (4070, 'a58ekv7lmy4dx0tqjzs2iwr9', '6v7be19pwman2fird04gqu53', '阅读「分享」中的使用案例,为新产品发布计划建立一个公示板吧!', '更新了内容 ', 'name', '2018-12-30 13:31:43', '0tjma1un2gz8rf4ywo7c6de9', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4071, '4isl2cgedqz7utjo6kbv81ar', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 15:30:09', 's5bxrym2p8qtjch1i6gnkvaw', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4072, 'phm3ed5l1vogjycaqntxwk8b', '6v7be19pwman2fird04gqu53', '123', '创建了任务 ', 'create', '2018-12-30 15:38:02', 'n9pe164krv8zghofilw750jq', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4073, 'ca8ixjvhk3p40mfqo7lue6gy', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 15:38:03', 'n9pe164krv8zghofilw750jq', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4074, 'yz4urfgdkslc9jahq73vwt01', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 15:38:35', 'n9pe164krv8zghofilw750jq', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4075, 'z26cd0s5ntgj7prh1x9vk3uq', '6v7be19pwman2fird04gqu53', '444', '创建了任务 ', 'create', '2018-12-30 15:39:15', 'oq7d3wbklenf12pgvuxhimr8', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4076, 'prgjo7vtns9k6qha43b0uled', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-30 15:39:15', 'oq7d3wbklenf12pgvuxhimr8', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4077, 'yp26orn0w4am59xl3utiqsdg', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2018-12-30 15:39:19', 'oq7d3wbklenf12pgvuxhimr8', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4081, '63a1jteb4qflz9pnig8c2k5u', '6v7be19pwman2fird04gqu53', '', '清除了截止时间 ', 'clearEndTime', '2018-12-30 22:15:33', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4082, 'urzst152ka3ep4w7ndm06qyg', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月28日 18:00', 'setEndTime', '2018-12-30 22:15:36', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4083, 'xbf1rpj8nmv7l9u4d3w0qke5', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 08:50:07', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4084, 'mroi3xe1wchq9t2p0bvg7uf5', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 08:50:10', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4085, 'iok3hc24sn6eftjr0lx9b7dv', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:18:10', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4086, 'ukx5mvn7lihws3gobc2ty1pf', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月01日 18:00', 'setEndTime', '2018-12-31 09:20:39', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4087, '21y6n8o9f5p3xa7ihkglqrv0', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:27:09', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4088, 'v4g7qblyhwuat5p2c3znor1s', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 18:00', 'setEndTime', '2018-12-31 09:28:02', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4089, 'f86ehji9spwlaubgv4m2qnz5', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月29日 18:00', 'setEndTime', '2018-12-31 09:28:18', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4090, 'y3x41uv5tacmnpq7gofji28r', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月23日 18:00', 'setEndTime', '2018-12-31 09:28:30', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4091, 'em5bhyangxtiu7voczpwd3js', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:31:09', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4092, 'nbteasyp9hfg3z06wvkj71qi', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 18:00', 'setEndTime', '2018-12-31 09:32:06', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4093, 'ba26fwrk5q8x0tsvn4hmoedi', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月23日 18:00', 'setEndTime', '2018-12-31 09:38:24', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4094, 'ionamswe2gj7hzdqx9br01f8', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月28日 18:00', 'setEndTime', '2018-12-31 09:38:27', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4095, 'l6fhtq2gneva8yj571cd43zu', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月03日 18:00', 'setEndTime', '2018-12-31 09:38:31', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4096, 'ifroz4bhkla789quv6x3dcsm', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:45:14', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4097, 'ob0m4yrs6uglxdkatfqj52zw', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月30日 18:00', 'setEndTime', '2018-12-31 09:45:22', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4098, 'm6faqh85rlxvedy3cpzujnbw', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 09:46:20', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4099, 'slroc7k1x3twdva50m28hf6p', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月01日 18:00', 'setEndTime', '2018-12-31 09:46:26', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4100, 'ncyk4b5ozwvuhrxd1sgp386f', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月08日 18:00', 'setEndTime', '2018-12-31 09:49:53', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4101, 'c5fynatdm0wgvqk1rhijsopz', '6v7be19pwman2fird04gqu53', '', '添加了参与者 vilson', 'inviteMember', '2018-12-31 10:49:34', 'mv4usefb06dxv8ez2spkl223', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4102, '51hc28jqn64zyspxlwmvrgak', '6v7be19pwman2fird04gqu53', '', '添加了参与者 Chihiro', 'inviteMember', '2018-12-31 10:49:34', 'mv4usefb06dxv8ez2spkl223', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4103, '96rdvspjbz1qfnxcogeam7h8', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 14:07:44', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4104, '05k4jw2tv9zu67yhbd1oigsr', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 14:08:02', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4105, '186hgmstdjxa2b50cqovpfez', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 14:08:10', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4106, 'dkx85glpe20r7uw9osa13z4j', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 14:19:55', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4107, 'bw3iz9a6fc2ylq5sngxp8md7', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-31 14:26:54', 'mv4usefb06dxv8ez2spkl223', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4108, 'uy8b34ph5qlas0t6cej7wfir', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2018-12-31 14:55:20', 'mv4usefb06dxv8ez2spkl223', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4109, 'tawsz234c90xm71ko5yg6uev', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:00:09', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4110, 'kysizpn5rf2t90o3jvgw7ubl', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2018-12-31 15:04:04', 'mv4usefb06dxv8ez2spkl223', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4111, 'wgzq1pyexs8lk9jinh5btvm2', '6v7be19pwman2fird04gqu53', '1', '创建了任务 ', 'create', '2018-12-31 15:04:43', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4112, '83rkucdwm5xos7aqlztjn4ih', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:43', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4113, 'khgazoxwlesn325uftdyb948', '6v7be19pwman2fird04gqu53', '2', '创建了任务 ', 'create', '2018-12-31 15:04:44', 'rzpu5cxl63fvb2y8gwdnsjqk', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4114, 'm0iwj9q5duxbsnarp86tk31h', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:44', 'rzpu5cxl63fvb2y8gwdnsjqk', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4115, 'i603csgvdzwh7ouajbfp2ren', '6v7be19pwman2fird04gqu53', '3', '创建了任务 ', 'create', '2018-12-31 15:04:45', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4116, 'hpycx4681ijk3bn59gzsml2a', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:45', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4117, 'ncp9o3q2y4mzuvx7e5rb0jga', '6v7be19pwman2fird04gqu53', '4', '创建了任务 ', 'create', '2018-12-31 15:04:46', 'xejt6431q8ly97bkid5z2pun', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4118, 'kyrlbu51ofmtsv9qax82p0ni', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:46', 'xejt6431q8ly97bkid5z2pun', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4119, 'wdm01ato3hpjfubvc9n4z7ge', '6v7be19pwman2fird04gqu53', '5', '创建了任务 ', 'create', '2018-12-31 15:04:47', 'zkqb6if5ogdts27lx13r4yju', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4120, 'rklwiogfxd48qytpvne5zhmu', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:47', 'zkqb6if5ogdts27lx13r4yju', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4121, 'w581csf97ojqytu0hderx4bz', '6v7be19pwman2fird04gqu53', '6', '创建了任务 ', 'create', '2018-12-31 15:04:49', '9wsohy8jgapl6x2iutbm7k34', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4122, '93047cqlbavx8wr2puie61zg', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2018-12-31 15:04:49', '9wsohy8jgapl6x2iutbm7k34', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4123, 'zmf9i5tv6ryuxjol71e3hw40', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 12月31日 18:00', 'setEndTime', '2018-12-31 15:05:09', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4124, 'xzduq1jiesmht89vkfo0p645', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 紧急', 'pri', '2018-12-31 15:05:22', 'qscug70y98zpk6edbnf3livr', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4125, 'dgt7mbakn1zx5huv93le4rj0', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2018-12-31 15:20:11', '9wsohy8jgapl6x2iutbm7k34', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4126, 'dq4j60pcg9st1maxwv7hozki', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:22:38', 'xkqg60sld15fcphwt4ya3rb8', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4127, 'brc67fhgxdsmv9kojtpan4ez', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 15:22:40', 'xkqg60sld15fcphwt4ya3rb8', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4128, 'fvxzwo24tur1np3s07qdbya9', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:23:01', 'xkqg60sld15fcphwt4ya3rb8', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4129, 'in2b7tx1pmfwjkhl38v54zoc', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 15:23:03', 'xejt6431q8ly97bkid5z2pun', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4130, 'lbt86sej72gwca0uno4kdh51', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2018-12-31 15:23:08', 'xejt6431q8ly97bkid5z2pun', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4131, 'j7xqfgbtns0huywr9po4z1va', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2018-12-31 17:55:13', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4132, 'pykstuo3210bn5iq76jg8wfz', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2018-12-31 17:55:15', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4133, 'me1ofzi6acr72hkb4pwtyvjg', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2019-01-01 12:41:11', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4134, 'uim9z48hg0p5jro23sfkxe67', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-01 12:41:13', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4135, 'gyrtq8b26ocxu3kdn4ef07z5', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2019-01-01 12:41:13', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4136, 's2unk6cav84ozqg7dtb10mrf', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-01 21:20:22', 'mv4usefb06dxv8ez2spkl223', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4137, 'tqbgjrp6vd8iks0hfnm91wc7', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 10:26:45', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4138, 't7eor5w2x6sf4u3n8hajlgic', 'kqdcn2w40p58r31zyo6efjib', '1', '创建了任务 ', 'create', '2019-01-03 10:46:04', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4139, 'evbyapm7qck64i89j3rgohw5', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-03 10:46:04', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4140, 'v20o3mcpb65awy4zf8le7h1i', 'kqdcn2w40p58r31zyo6efjib', '2', '创建了任务 ', 'create', '2019-01-03 10:46:13', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4141, '5ori2dcnubymxgz7j3968s1e', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-03 10:46:14', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4142, '7h6fiyn52vmt1kcqs9pgolab', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 10:49:35', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4143, 'qakjgy7sxw25rvnize8pfdtm', 'kqdcn2w40p58r31zyo6efjib', '', '重做了任务 ', 'redo', '2019-01-03 10:49:37', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4144, 'qehcwy2mupsi8jrb4105lv7t', 'kqdcn2w40p58r31zyo6efjib', '', '添加了参与者 vilson', 'inviteMember', '2019-01-03 10:51:44', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4145, 'tmr1xvy4hz97fj8nsegoib0w', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-03 10:51:51', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4146, '8fubhgml3dx16krpto2e5sv9', 'kqdcn2w40p58r31zyo6efjib', '', '移除了执行者 ', 'removeExecutor', '2019-01-03 10:51:53', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4147, 'qgsbed0ozp27nwirlfk8yatj', 'kqdcn2w40p58r31zyo6efjib', '

66

', '更新了备注 ', 'content', '2019-01-03 10:52:06', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4148, 'zh3k4aotncqu2bw16yr75mfg', 'kqdcn2w40p58r31zyo6efjib', '', '移除了参与者 vilson.2', 'removeMember', '2019-01-03 10:58:16', 'aut9wrz1pn0elf5s47ivx26o', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4149, 'zfewmyds7q14li2vj56rngpc', 'kqdcn2w40p58r31zyo6efjib', '99', '创建了任务 ', 'create', '2019-01-03 11:00:15', 'm6cloqrbh7tf0wg1jsvp9nay', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4150, 'h7pilc01go6a2qj5e3stxwzb', 'kqdcn2w40p58r31zyo6efjib', '', '添加了子任务 \"99\"', 'createChild', '2019-01-03 11:00:15', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4151, '9kpj26uxbdetlgiqcsm5a8zh', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 Chihiro', 'assign', '2019-01-03 11:00:15', 'm6cloqrbh7tf0wg1jsvp9nay', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4152, 'd1onu8sbwl62hy9tcpjkfeaq', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 11:01:30', 'm6cloqrbh7tf0wg1jsvp9nay', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4153, '148jmclitfbvkx95ry2ed3nz', 'kqdcn2w40p58r31zyo6efjib', '', '完成了子任务 \"Tree 的 @on-select-change 和 @on-check-change 事件返回参数新增当前项\"', 'doneChild', '2019-01-03 11:01:30', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4154, 'rchx5tm41wposf780yjz6egu', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 11:16:12', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4155, '9qnt6l7hej1s4i238pukxvmr', 'kqdcn2w40p58r31zyo6efjib', '', '重做了任务 ', 'redo', '2019-01-03 11:16:13', 'q9y6ksvtifwpuhna0e32jgm1', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4156, 'a6psv8onwmyq1e2i0g95bk3d', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-03 11:16:15', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4157, '6xqhe5l4pokcg0m2v7yd1tbs', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-03 11:16:18', 'wyklgmhpt5qr47x3zsf9nibj', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4158, 'lv23ua5jdypmoes7cgz89rxb', '6v7be19pwman2fird04gqu53', '增加了一个新组件 Comment', '创建了任务 ', 'create', '2019-01-03 22:25:29', 'p1aujdigrlxky76h8cs3z4w0', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4159, 'mnbz7hw2oi94u6fk3r5xqpdc', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:30', 'p1aujdigrlxky76h8cs3z4w0', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4160, 'basihz6npvt2xf71reog45ud', '6v7be19pwman2fird04gqu53', '增加了一个新组件 ConfigProvider 为组件提供统一的全局化配置', '创建了任务 ', 'create', '2019-01-03 22:25:37', '2bn918l6ejyzousa73dkpgci', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4161, '9lgmtpoj613bwevfasq4i0dk', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:37', '2bn918l6ejyzousa73dkpgci', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4162, 'z4cx5uvmfsjakr2b38w0geld', '6v7be19pwman2fird04gqu53', 'Avatar 组件增加 srcSet 属性,用于设置图片类头像响应式资源地址', '创建了任务 ', 'create', '2019-01-03 22:25:45', '3qz5hfsin69xt8cgbd70lkew', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4163, 'bde7mgz60ylicx4tap1rf82w', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:45', '3qz5hfsin69xt8cgbd70lkew', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4164, '3oktw2hblvn9f7e8mj01rduz', '6v7be19pwman2fird04gqu53', '增加 less 变量 @font-variant-base 定制 font-variant 样式', '创建了任务 ', 'create', '2019-01-03 22:25:53', 'xkic58d20srnu9jm7ohqw14f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4165, '8721mxbo0quri9jydg3halwv', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:25:53', 'xkic58d20srnu9jm7ohqw14f', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4166, 'cld81p5er0bsxnmj934w7qgf', '6v7be19pwman2fird04gqu53', '优化鼠标悬停在可排序的表头上时 title 的显示', '创建了任务 ', 'create', '2019-01-03 22:26:01', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4167, 'efj1xhpr9i5a0tq7y4knzub2', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:01', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4168, 'xelf9v7kzin6tj5bqoyaw1rg', '6v7be19pwman2fird04gqu53', '修正 Comment author 属性的类型为 ReactNode', '创建了任务 ', 'create', '2019-01-03 22:26:09', 'twb8f52jasn9vry6iko0dqg4', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4169, 'uiq9yv85f7zgba01w4ln6x3r', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:09', 'twb8f52jasn9vry6iko0dqg4', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4170, 'f10hn732taevsdpygjbcl5w8', '6v7be19pwman2fird04gqu53', '优化 Spin 样式并略微提升了切换状态的性能', '创建了任务 ', 'create', '2019-01-03 22:26:16', 'gjmotpbrwva079ukde4izn38', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4171, 'q237mcahkzp1sd8ij4ry6fvn', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:16', 'gjmotpbrwva079ukde4izn38', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4172, '9tc5erp8lgdxon1muhas4wyk', '6v7be19pwman2fird04gqu53', '微调 Card 头部和加载中的样式细节', '创建了任务 ', 'create', '2019-01-03 22:26:21', 'uwq87z2f0hnvrl6o9gtcb3iy', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4173, 'im06nuj924kzy3lhwqert8vp', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:26:21', 'uwq87z2f0hnvrl6o9gtcb3iy', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4174, 'zj73kmhg5yicqlse09v824dn', '6v7be19pwman2fird04gqu53', 'Notification 组件升级 rc-notification 到 3.3.0,增加 onClick 属性,点击通知时触发的回调函数', '更新了内容 ', 'name', '2019-01-03 22:26:36', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4175, 'ohl1jfb4wqkcad0gs76my9n5', '6v7be19pwman2fird04gqu53', 'Notification 组件升级 rc-notification 到 3.3.0', '更新了内容 ', 'name', '2019-01-03 22:26:50', 'mv4usefb06dxv8ez2spkl223', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4176, 'ubrczedhjpwfv5ygnl1089s2', '6v7be19pwman2fird04gqu53', 'Cascader 升级 rc-calendar', '创建了任务 ', 'create', '2019-01-03 22:27:04', 'qug5e4alndm7930ipxwyvc2h', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4177, '71veqsi6pomnlz4fd3k85x90', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:04', 'qug5e4alndm7930ipxwyvc2h', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4178, 'fvc1z3rbgslkhwto498qpie0', '6v7be19pwman2fird04gqu53', 'Upload 组件升级 rc-upload 到 2.5.0', '创建了任务 ', 'create', '2019-01-03 22:27:17', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4179, 'a5ixq9onv1h0sm2ydr6wbult', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:17', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4180, '8jnxw6ot03yb5ms1lae9ivuq', '6v7be19pwman2fird04gqu53', '重构 Tag 组件,简化代码并提升性能', '创建了任务 ', 'create', '2019-01-03 22:27:30', 'm7u8fdp41cwrtkjxyzq2ion3', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4181, 'lo3fs4d610v98imxaertkcgw', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:30', 'm7u8fdp41cwrtkjxyzq2ion3', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4182, '2t8wh5zk3ad79rb4q0oijm1g', '6v7be19pwman2fird04gqu53', 'Badge 进行了重构,count 支持自定义组件', '创建了任务 ', 'create', '2019-01-03 22:27:36', 'jo0i8fq2579kbdgsmcw1nev4', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4183, '9mvrcztjylsghin1qwae8o67', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:36', 'jo0i8fq2579kbdgsmcw1nev4', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4184, 'phb9kdnt45ir32vojcmwl1g6', '6v7be19pwman2fird04gqu53', '重构了 Tree 底层的代码,以解决一些存在了很久的问题', '创建了任务 ', 'create', '2019-01-03 22:27:54', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4185, '3q4sxlb6jogair159w8h0u27', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:27:54', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4186, 'sufbt1amn9c8eqd6yil7rvx0', '6v7be19pwman2fird04gqu53', '修复了 Badge 代码错误引起的 TypeScript 类型报错', '更新了内容 ', 'name', '2019-01-03 22:28:13', 'aut9wrz1pn0elf5s47ivx26o', 'task', '', 0, NULL, 'edit'); +INSERT INTO `pear_project_log` VALUES (4187, 'q5lpxoh7mrzj2cbgtv0u4698', '6v7be19pwman2fird04gqu53', '修复了 Divider 与浮动元素一起使用时的样式问题', '创建了任务 ', 'create', '2019-01-03 22:28:21', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4188, 'tog9yrh7ekjc6q31fanmxwpz', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:21', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4189, 'gf0psdotxqblcy918wznaih7', '6v7be19pwman2fird04gqu53', '修复了 Form 高级搜索模式下的样式问题', '创建了任务 ', 'create', '2019-01-03 22:28:25', '0a84xkg12enqjml7rz6dbifw', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4190, 'ob51ay942rmpgk8c3vxlftdn', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:25', '0a84xkg12enqjml7rz6dbifw', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4191, 'fb9uj15xn2zs6wihpo8lmecy', '6v7be19pwman2fird04gqu53', '修复了 Upload 对无扩展名图片地址的预览展示问题', '创建了任务 ', 'create', '2019-01-03 22:28:29', 'fax4gez2jlk15tvsu3dc6p98', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4192, 'kjs527dh3qfzuolyt1i0map9', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:29', 'fax4gez2jlk15tvsu3dc6p98', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4193, 'wefjuogckp7n90qm6384bhrz', '6v7be19pwman2fird04gqu53', '修复一处 less 语法错误', '创建了任务 ', 'create', '2019-01-03 22:28:33', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4194, 'u3986mxveapdh7oj1rift2zg', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:33', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4195, 'bwtvydrazh8emo1x9up3sj27', '6v7be19pwman2fird04gqu53', '修复 LocaleProvider 中 moment.locale 调用报错的问题', '创建了任务 ', 'create', '2019-01-03 22:28:39', 'jiy25eobh1cnp7ruvg9d0m6s', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4196, 'ri6tevk1y7m2zsjwuqa45g3c', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:39', 'jiy25eobh1cnp7ruvg9d0m6s', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4197, 'ow3r8pkn7bet16x9jlh5a0d4', '6v7be19pwman2fird04gqu53', '修复 WeekPicker 的 style 属性不生效的问题', '创建了任务 ', 'create', '2019-01-03 22:28:43', '4pv9brqnm0cigwu5f3zeyxdk', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4198, 'ncagwsky8df3lm61027ore45', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:43', '4pv9brqnm0cigwu5f3zeyxdk', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4199, 'bnry5391jzg84sd72pthxlfv', '6v7be19pwman2fird04gqu53', 'Carousel: 升级 react-slick 版本以修复宽度计算错误', '创建了任务 ', 'create', '2019-01-03 22:28:50', 'td1qznl9ms65gbcfej0k4vup', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4200, 'qx8fcy1s37mo4th5kwvb69d2', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:50', 'td1qznl9ms65gbcfej0k4vup', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4201, '3d8szi6qx7t9v5ljc1yueag4', '6v7be19pwman2fird04gqu53', '修复 enterButton 的值为 button 元素时显示错误的问题', '创建了任务 ', 'create', '2019-01-03 22:28:56', 'fkrsvpzmj8xyo045hiugqt92', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4202, 'lgdti7me3ao62hxy18cpurz9', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:28:56', 'fkrsvpzmj8xyo045hiugqt92', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4203, '7apl461qkdgm9ui5jftsobe8', '6v7be19pwman2fird04gqu53', '修复表单校验文字消失的时候输入框会抖一下的问题', '创建了任务 ', 'create', '2019-01-03 22:29:02', '0b6wlc3754fr8gdvupx9aoys', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4204, 'gr8ibowa7mvu4k5031z9tqfe', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:29:02', '0b6wlc3754fr8gdvupx9aoys', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4205, 'ie2jn0u4dxbszq9wtyvc7r85', '6v7be19pwman2fird04gqu53', '重构了 DatePicker 相关 type 定义', '创建了任务 ', 'create', '2019-01-03 22:29:18', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4206, 'm9qkjn18zs0u3f6grpae2wo4', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:29:18', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4207, 'bxf0ydu7zcp2g5h1jaekinol', '6v7be19pwman2fird04gqu53', 'Steps 进行了重构,首次渲染的时候不会再闪烁', '创建了任务 ', 'create', '2019-01-03 22:29:24', 'hxntygarp3094c7w1856iujm', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4208, 'rq7em12fog9ulpcjiynx3zv0', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-03 22:29:24', 'hxntygarp3094c7w1856iujm', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4209, 'dkq4c2thuzwopgy8l5vrj3es', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:29:42', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4210, 'm5jgeyqv168nrzitb2xf0wuc', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-03 22:30:00', '0a84xkg12enqjml7rz6dbifw', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4211, '93auio1dnj5t7qzf0lb2kxh4', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2019-01-03 22:30:07', 'jiy25eobh1cnp7ruvg9d0m6s', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4212, 'mf4zgw5j60atlxh1pndko38s', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:30:15', 'td1qznl9ms65gbcfej0k4vup', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4213, 'l4fdhpo65mx0ji3geu7y8qsn', '6v7be19pwman2fird04gqu53', '', '移除了执行者 ', 'removeExecutor', '2019-01-03 22:30:22', 'twb8f52jasn9vry6iko0dqg4', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user-delete'); +INSERT INTO `pear_project_log` VALUES (4214, 'ukx9ozqe8d27cvbifrwmjy54', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:30:27', 'uwq87z2f0hnvrl6o9gtcb3iy', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4215, 'x2o3euprgi1nzf8mt70kyl6s', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-03 22:30:31', '3qz5hfsin69xt8cgbd70lkew', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4216, 'kyg8xpuovtaw2lc91765fn4j', '6v7be19pwman2fird04gqu53', '', '指派给了 vilson.2', 'assign', '2019-01-03 22:30:38', 'xkic58d20srnu9jm7ohqw14f', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4217, 'a53r0dg7o2cbli4pfqewuy8m', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-03 22:30:46', 'owrs04m3e2klj8uqac6tiy17', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4218, 'r18ygstu75v4d2xewqfckhln', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:30:57', 'bl1t7xjwpi9m2aocnsz83fk6', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4219, '3jxrfzquwc29yptg1bovm8i7', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:30:59', 'hxntygarp3094c7w1856iujm', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4220, '96wnyl4kut1sdxj2bzop3ivr', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:30:59', 'gjmotpbrwva079ukde4izn38', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4221, 'vmxe7qcidhsrpn3k94wb2af5', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:31:02', 'xkic58d20srnu9jm7ohqw14f', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4222, 'jf65y3tiredw8lkh1xpgzb0q', '6v7be19pwman2fird04gqu53', '', '完成了任务 ', 'done', '2019-01-03 22:31:07', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4223, 'gaihuw7petn3y165lcjvrk42', '6v7be19pwman2fird04gqu53', '', '重做了任务 ', 'redo', '2019-01-03 22:31:13', 'zv4hx1ugpn98be5skc3wym72', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4224, 'w4jxovd6bsfinz0tq7rhleau', '6v7be19pwman2fird04gqu53', '
  •  I have searched the issues of this repository and believe that this is not a duplicate.

Version

3.10.9

Environment

Any every green browsers

Reproduction link

\"Edit

Steps to reproduce

Please see the linked example in \"Link to minimal reproduction\".
The hover over message on column \"Name\" is \"sort\", however, it should be \"hello\" based on the code logic.

What is expected?

When using a Popover for the title of a column, the hover over message on that column\'s header should be the value passed to Popover\'s title prop instead of \"sort\"

What is actually happening?

The hover over message on all sortable columns is \"sort\".

', '更新了备注 ', 'content', '2019-01-04 08:54:41', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'file-text'); +INSERT INTO `pear_project_log` VALUES (4225, 'ezcbv40idx8urkswg7yoj2mq', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 紧急', 'pri', '2019-01-04 08:56:41', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4226, 'sbal18knrhvtwj0zc5g4i3dq', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月06日 18:00', 'setEndTime', '2019-01-04 08:56:45', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4227, '1b6h9dnxq27w48eygut5acmk', '6v7be19pwman2fird04gqu53', '', '添加了参与者 Chihiro', 'inviteMember', '2019-01-04 08:57:04', '6hj43ueim2bk187sqzcoy59v', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user-add'); +INSERT INTO `pear_project_log` VALUES (4228, 'o8khyma5vz9rbs63pdwx7iej', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 08:57:09', '6hj43ueim2bk187sqzcoy59v', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4229, '6f5g1qt4oj98ysrdhawx2c3k', '6v7be19pwman2fird04gqu53', 'this seems more like a question to @afc163 , but here\'s my two cents: it might be better to add an additional prop called something like hoverOverTitle so that it will show up when you hover over on a column header. If the consumer uses a Popover for column\'s title, the hover over behavior should honor whatever sets in that Popover component.', NULL, 'comment', '2019-01-04 08:57:35', '6hj43ueim2bk187sqzcoy59v', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4230, '90l6k8b7hngpymoacietrf1j', 'kqdcn2w40p58r31zyo6efjib', 'I want to take this and have a question,\n\nwhen hover on column Age, it should show Sort or Age?', NULL, 'comment', '2019-01-04 09:04:33', '6hj43ueim2bk187sqzcoy59v', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4231, 'fvtby5am906l87g1cxjshe32', 'y680trgedcavbhnz24u7i5m3', 'In my opinion, when type of title is string, it should show Sort, if it\'s a ReactNode, it should show the last step of prop title, maybe add prop is not a good way for user.', NULL, 'comment', '2019-01-04 09:08:23', '6hj43ueim2bk187sqzcoy59v', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4232, 'sxe2dy5wcb6hn38mig97zavq', 'y680trgedcavbhnz24u7i5m3', 'Change hover over message of Column', '创建了任务 ', 'create', '2019-01-04 09:09:28', 'gk8ipqm5406br7cwd9l1zefs', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4233, '56l3m84euzs7cypgb9qfijnv', 'y680trgedcavbhnz24u7i5m3', '', '添加了子任务 \"Change hover over message of Column\"', 'createChild', '2019-01-04 09:09:28', '6hj43ueim2bk187sqzcoy59v', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4234, 'kyeuzfgnxoms658bd3tq12p7', 'y680trgedcavbhnz24u7i5m3', '', '认领了任务 ', 'claim', '2019-01-04 09:09:29', 'gk8ipqm5406br7cwd9l1zefs', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4235, '98p7g6n1ltzbfre3oxkyhv2d', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-04 09:17:51', 'm7u8fdp41cwrtkjxyzq2ion3', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4236, '8rvy3inbt46ckxu59fld7pqz', 'kqdcn2w40p58r31zyo6efjib', '新增 directory 属性,支持上传一个文件夹', '创建了任务 ', 'create', '2019-01-04 09:18:18', 'o61b3s24exmcy8njkparwthd', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4237, 'nkr94lumsqbgi2ofhp7d8c5x', 'kqdcn2w40p58r31zyo6efjib', '', '添加了子任务 \"新增 directory 属性,支持上传一个文件夹\"', 'createChild', '2019-01-04 09:18:18', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4238, 'zy5t9lmv671r4qxf8bpcwjsn', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-04 09:18:18', 'o61b3s24exmcy8njkparwthd', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4239, 'yp58fhjvoxanqbtirwz92m3c', 'kqdcn2w40p58r31zyo6efjib', 'action 属性支持作为一个返回 Promise 对象的函数,使用更加灵活', '创建了任务 ', 'create', '2019-01-04 09:18:24', 'orycwlhf7n2qx1pta038dzjk', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4240, 'sm4bhc5v2fqo70ja6twrzyuk', 'kqdcn2w40p58r31zyo6efjib', '', '添加了子任务 \"action 属性支持作为一个返回 Promise 对象的函数,使用更加灵活\"', 'createChild', '2019-01-04 09:18:24', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'bars'); +INSERT INTO `pear_project_log` VALUES (4241, 'e9k6u3vtrwb48njmzhgipcxl', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 vilson', 'assign', '2019-01-04 09:18:24', 'orycwlhf7n2qx1pta038dzjk', 'task', '6v7be19pwman2fird04gqu53', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4242, 'e8o2chmva49yb670g5ld1n3j', 'kqdcn2w40p58r31zyo6efjib', '', '认领了任务 ', 'claim', '2019-01-04 09:18:50', 'yctbsv81x6dmahkf7ei5o4r9', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4243, 'yxv4b1kd0zntahm63sfj2cpi', 'kqdcn2w40p58r31zyo6efjib', '', '更新任务优先级为 非常紧急', 'pri', '2019-01-04 09:19:16', '3qz5hfsin69xt8cgbd70lkew', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4244, 'gxq2b46fh7t8rudnvcsimeaz', 'kqdcn2w40p58r31zyo6efjib', '', '更新截止时间为 01月04日 18:00', 'setEndTime', '2019-01-04 09:19:42', '0a84xkg12enqjml7rz6dbifw', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4245, '053rfobwtegvy7mnq4s8k1c9', 'kqdcn2w40p58r31zyo6efjib', '', '更新截止时间为 01月01日 18:00', 'setEndTime', '2019-01-04 09:19:51', 'p1aujdigrlxky76h8cs3z4w0', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4246, 'dyj9z6wvmrh84bx07ofsacnq', 'kqdcn2w40p58r31zyo6efjib', '', '指派给了 Chihiro', 'assign', '2019-01-04 09:19:58', 'p1aujdigrlxky76h8cs3z4w0', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4247, 'usbyo9z8x12frq7lgmvp0tk3', 'y680trgedcavbhnz24u7i5m3', '', '完成了任务 ', 'done', '2019-01-04 09:23:11', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4248, 'undfg6m57a1wkojy0i9l4bcq', 'y680trgedcavbhnz24u7i5m3', '', '重做了任务 ', 'redo', '2019-01-04 09:23:12', 'ozi8awms1lpcbde4fuq5ktgj', 'task', '', 0, NULL, 'border'); +INSERT INTO `pear_project_log` VALUES (4249, 'u5j06pz2gqky9m8341aervnc', '6v7be19pwman2fird04gqu53', 'Add variant prop and deprecate fullWidth and scrollable props', '创建了任务 ', 'create', '2019-01-04 21:17:13', 'up6hn9bd34c8mglwaj1ytefz', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4250, 'hv9jscyrzu6fb5q1xpnm7wdl', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:17:13', 'up6hn9bd34c8mglwaj1ytefz', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4251, 'ecb7pyij8q9w42tkfgh3s5xv', '6v7be19pwman2fird04gqu53', 'Add styles to make size property work with extended property', '创建了任务 ', 'create', '2019-01-04 21:17:19', 'krj4p7ix2cf605vyltmudq1e', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4252, 'smkh91f8r4p5ayjqxnl2utgz', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:17:19', 'krj4p7ix2cf605vyltmudq1e', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4253, 'vi0nwtrzx62bqluc9hd1pf73', '6v7be19pwman2fird04gqu53', 'Add cross references from Modal docs to other components', '创建了任务 ', 'create', '2019-01-04 21:17:46', '1g3vc8tkyla20fp5rdhxe7mo', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4254, 'h18qzjuxwfsrtck5gb37dey2', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:17:46', '1g3vc8tkyla20fp5rdhxe7mo', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4255, 'lnd73yz9tp4sha8mgc6jbofx', '6v7be19pwman2fird04gqu53', 'Add createSvgIcon type definition', '创建了任务 ', 'create', '2019-01-04 21:18:25', 'nqrleu2c90zsdaj1yph4m8bt', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4256, '69fd13ekwabg072incpjtzxq', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:18:25', 'nqrleu2c90zsdaj1yph4m8bt', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4257, 'fwcql690r7pd2jmoit53h1sy', '6v7be19pwman2fird04gqu53', 'Add customized demo', '创建了任务 ', 'create', '2019-01-04 21:18:37', 'mix3cg2eh1u60fknd7yz9v5t', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4258, 'o7xejsdzrcygia48bflh2mn9', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:18:37', 'mix3cg2eh1u60fknd7yz9v5t', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4259, 'jibgsz3vh728qpxdnywtalmc', '6v7be19pwman2fird04gqu53', 'Add defaultTheme option for makeStyles', '创建了任务 ', 'create', '2019-01-04 21:18:45', 'dckxz1vpujtafshgr20mwo7e', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4260, 'i1d3zsf5hb4ujwvcy6lp09rg', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:18:45', 'dckxz1vpujtafshgr20mwo7e', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4261, 'faneshd15vcx782mpo4qjrz0', '6v7be19pwman2fird04gqu53', 'Add nextjs-hooks-with-typescript', '创建了任务 ', 'create', '2019-01-04 21:18:53', 'fd1avskez2q43w80xhb7ypc9', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4262, 'jxfa2oiru6by80lg3hcv4s9q', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:18:53', 'fd1avskez2q43w80xhb7ypc9', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4263, '036qie8nhvp1wmjs4bfgkua5', '6v7be19pwman2fird04gqu53', 'Add note on archived components', '创建了任务 ', 'create', '2019-01-04 21:19:01', 'as2y4r6mwxuhgvncop3f8z90', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4264, 'fjb157v0mexsh8qwig2ay3n4', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:19:01', 'as2y4r6mwxuhgvncop3f8z90', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4265, 'q7majphuec3wlbyig9r4kons', '6v7be19pwman2fird04gqu53', 'Add Instagram theme', '创建了任务 ', 'create', '2019-01-04 21:19:08', '8zj3vpx0b7qud24ylfgces1m', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4266, 'egxlqbr1d27p49yafkv3cn0u', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:19:08', '8zj3vpx0b7qud24ylfgces1m', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4267, 'ebpjx21r0yhkwcsm59gn3auo', '6v7be19pwman2fird04gqu53', 'Add component prop', '创建了任务 ', 'create', '2019-01-04 21:19:18', 'hcrdvbuzwgojst2f0p134qxi', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4268, 'kd3bmhtc0nwi9ojeu2axgqrz', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:19:18', 'hcrdvbuzwgojst2f0p134qxi', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4269, 'af0puzmxh5dvi1yr8c2q96kw', '6v7be19pwman2fird04gqu53', 'Fix utils.chainPropTypes issue', '创建了任务 ', 'create', '2019-01-04 21:19:37', 'lmognshqz21dbewcu9a3rx87', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4270, 'subp5hco4z30x8ml629wtv7a', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:19:37', 'lmognshqz21dbewcu9a3rx87', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4271, 'ig7evacluxktjm865oy0241w', '6v7be19pwman2fird04gqu53', 'Fix vertical text alignment by reducing padding', '创建了任务 ', 'create', '2019-01-04 21:19:51', 'n6ulc7ebxpqahi50dy9k1sgf', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4272, 'c734zkl1hs9ef5p8vmgrow2y', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:19:51', 'n6ulc7ebxpqahi50dy9k1sgf', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4273, 'blspvm6321oqxw5ekd8fgrzh', '6v7be19pwman2fird04gqu53', 'Fix infinite loop in the scroll button logic', '创建了任务 ', 'create', '2019-01-04 21:19:57', 'rqjng1kfcp4wyiamt6o23zbu', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4274, 'ce7qy51oun6xsvgjz8lt29wp', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:19:57', 'rqjng1kfcp4wyiamt6o23zbu', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4275, 'pjo8kwxfz0t4s9riqc1a2y56', '6v7be19pwman2fird04gqu53', 'Fix component animations', '创建了任务 ', 'create', '2019-01-04 21:20:05', 'qsz65fvgi8hyx3e7bn14o9wm', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4276, 'wyx6dmp8aknejcf53ihu1ob9', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:20:05', 'qsz65fvgi8hyx3e7bn14o9wm', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4277, '4rdmf20pteglk9nsyqi3jb85', '6v7be19pwman2fird04gqu53', 'Fix responsivePropType typo', '创建了任务 ', 'create', '2019-01-04 21:20:12', 'byiuxhn0v6sod4zap1t2fclr', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4278, '48o1z0emd6chu29xk5iwansf', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:20:12', 'byiuxhn0v6sod4zap1t2fclr', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4279, 'srabgivxch13kuemzq9n58to', '6v7be19pwman2fird04gqu53', 'Change action element to have a fixed right margin', '创建了任务 ', 'create', '2019-01-04 21:20:27', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4280, 'z82lho53je6sfc7r9i0dnwpa', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:20:27', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4281, 'lti2ksapfd06rux3w5q9zgvc', '6v7be19pwman2fird04gqu53', 'Change height from 5 to 4 pixels', '创建了任务 ', 'create', '2019-01-04 21:20:33', 'vmzeciodgbfp7ysu38tq10kj', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4282, 'pkwhqt1ncf5o6xyjmu42irgb', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:20:33', 'vmzeciodgbfp7ysu38tq10kj', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4283, 'fcp6rwx0gklq8a7hz9b35ijd', '6v7be19pwman2fird04gqu53', 'Change sub-components to have fixed gutters', '创建了任务 ', 'create', '2019-01-04 21:20:45', '6cagd725tifonvw0qphe9zsb', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4284, '4fwn60cymibsg58vaxoleq37', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:20:45', '6cagd725tifonvw0qphe9zsb', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4285, '1ln8e40zfx6ok7jabdqcpr29', '6v7be19pwman2fird04gqu53', 'Change the classes structure to match the core components convention', '创建了任务 ', 'create', '2019-01-04 21:20:54', 'xu3jgyow2s9f1km0rctqin4v', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4286, 'jpwcd12ve5zgiq6ofu9r8atl', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:20:54', 'xu3jgyow2s9f1km0rctqin4v', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4287, 'protl2wye4km9zns3bq87icx', '6v7be19pwman2fird04gqu53', 'Update the action spacing to better match the spec', '创建了任务 ', 'create', '2019-01-04 21:21:12', 'k3g07m1qyctvbp95siohju6f', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4288, 'dayz0l5kr98s1fpnj7ohm623', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:21:12', 'k3g07m1qyctvbp95siohju6f', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4289, '038psj6uflqcn9y5ravhtbgd', '6v7be19pwman2fird04gqu53', 'Update the emotion documentation', '创建了任务 ', 'create', '2019-01-04 21:21:18', 'oh5wpj9kd8e6ltusxq271ma3', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4290, 'v7l5xz63qmy2sfc0igub18wp', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:21:18', 'oh5wpj9kd8e6ltusxq271ma3', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4291, 'ikpveu0dxl2fgrna16ms3tyo', '6v7be19pwman2fird04gqu53', 'Update the CodeFund embed script', '创建了任务 ', 'create', '2019-01-04 21:21:25', 'akdwslbtp3z82xecui0y4ovq', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4292, 'q1p8igck4a9nxo3l502d7h6z', '6v7be19pwman2fird04gqu53', '', '指派给了 Chihiro', 'assign', '2019-01-04 21:21:25', 'akdwslbtp3z82xecui0y4ovq', 'task', 'y680trgedcavbhnz24u7i5m3', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4293, 'y2unpg9qifrm14kd3x8tzvs6', '6v7be19pwman2fird04gqu53', 'Update react-select demo to have isClearable set to true', '创建了任务 ', 'create', '2019-01-04 21:21:31', 'hayfr6vl398nq5exgszobu2j', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4294, '75ecai2omdzqurl3jpy9w0th', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:21:31', 'hayfr6vl398nq5exgszobu2j', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4295, 'u910zdfm24ji3hoqstwnyvl7', '6v7be19pwman2fird04gqu53', 'Update album page-layout preview image album.png', '创建了任务 ', 'create', '2019-01-04 21:21:41', 'mf80iu15kepavbg2r9ldcjsh', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4296, '91gkpfrqeclbu5h8xdzn40mj', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:21:42', 'mf80iu15kepavbg2r9ldcjsh', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4297, 'ksw9y1dmvntue7o85q2xplig', '6v7be19pwman2fird04gqu53', 'Update some components to better match the Material specification', '创建了任务 ', 'create', '2019-01-04 21:22:08', 'nzy71f5i6g0skwau4lrj3d8b', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4298, 'jxbdmz1csi2e3w7aouvfnlrg', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:22:08', 'nzy71f5i6g0skwau4lrj3d8b', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4299, 'wx6fm0c9iea3qlvb7ounksd2', '6v7be19pwman2fird04gqu53', 'Remove hoisting of static properties in HOCs', '创建了任务 ', 'create', '2019-01-04 21:22:20', '4cug3e5rodalq9x81ywht0zn', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4300, 'zi16cj4gmyn9wfb72ktroxa0', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:22:20', '4cug3e5rodalq9x81ywht0zn', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4301, 'me0stqcvz5r48p29nyu76x1a', '6v7be19pwman2fird04gqu53', 'Remove the withRoot HOC', '创建了任务 ', 'create', '2019-01-04 21:22:24', '92fow0le47htb6xkv5ynzuri', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4302, 'rmce81xwt5f9sb7uv6o0k3yn', '6v7be19pwman2fird04gqu53', '', '指派给了 Alians', 'assign', '2019-01-04 21:22:24', '92fow0le47htb6xkv5ynzuri', 'task', 'kqdcn2w40p58r31zyo6efjib', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4303, 'lfeou15n7gzktw8xrdmqayh6', '6v7be19pwman2fird04gqu53', '100% remove the prop types', '创建了任务 ', 'create', '2019-01-04 21:22:34', '6ky18i9cg0eqvfzn2th3ux5l', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4304, '5o7unx3qt62s4impw8hyzl0k', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:22:34', '6ky18i9cg0eqvfzn2th3ux5l', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4305, 'r6j9of80pmdlatq2b5wzsx7i', '6v7be19pwman2fird04gqu53', 'Remove unused lint directives', '创建了任务 ', 'create', '2019-01-04 21:22:40', 'zj6skt9orn748gh5mvb2ueif', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4306, 'i3zehaf9ux1bpjqsmgl2w4kt', '6v7be19pwman2fird04gqu53', '', '认领了任务 ', 'claim', '2019-01-04 21:22:40', 'zj6skt9orn748gh5mvb2ueif', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4307, 'itoehcalwm75x86ndukgvbz9', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 非常紧急', 'pri', '2019-01-04 21:23:11', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4308, 'a60nuwtb2skd7mqpi5vroc89', '6v7be19pwman2fird04gqu53', 'If you install react-jss via npm, currently it installs with version 10.0.0-alpha.7 which has breaking changes included.\nSo I guess it should be updated with below? Correct me if I\'m wrong, thanks', NULL, 'comment', '2019-01-04 21:27:17', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4309, 'zv57agjqeymuw4tpkilrs0h1', 'kqdcn2w40p58r31zyo6efjib', 'The new JSS v10 alpha seems to have deprecated the react-jss library, or at least the github repo, and there seem to be breaking API changes as well, the migration path of which is not clear.', NULL, 'comment', '2019-01-04 21:27:30', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4310, '3lwyhv5tf4d2ec7a1qknp6zr', 'y680trgedcavbhnz24u7i5m3', 'yeah, npm tags is a mess right now, need to find a way to set them each time with our current setup over lerna publish or drop lerna and do it differently', NULL, 'comment', '2019-01-04 21:29:15', 'jxd3rpmay6qonsk1i8wg5e9u', 'task', '0', 1, NULL, NULL); +INSERT INTO `pear_project_log` VALUES (4312, 'jbex46ci20y781aswvgprdhm', 'y680trgedcavbhnz24u7i5m3', 'Improve demos loading', '创建了任务 ', 'create', '2019-01-04 21:30:13', 'a75dcqx2sjivokmg49yh380l', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4314, 'kt72j1eobvif5hdpzcmqxgy9', 'y680trgedcavbhnz24u7i5m3', '', '认领了任务 ', 'claim', '2019-01-04 21:30:13', 'a75dcqx2sjivokmg49yh380l', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4315, '0ycs8l7j123w4dgpoetka9qu', 'y680trgedcavbhnz24u7i5m3', 'Improve the service-worker logic', '创建了任务 ', 'create', '2019-01-04 21:30:19', '7ns924ofulpjxkgq06y3bm5r', 'task', '', 0, NULL, 'plus'); +INSERT INTO `pear_project_log` VALUES (4317, 'g3yznvlot84umieabq01p5ck', 'y680trgedcavbhnz24u7i5m3', '', '认领了任务 ', 'claim', '2019-01-04 21:30:19', '7ns924ofulpjxkgq06y3bm5r', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4319, '2qwgit8mpduzsanbckrvx97e', 'y680trgedcavbhnz24u7i5m3', '', '完成了任务 ', 'done', '2019-01-04 21:31:17', 'fd1avskez2q43w80xhb7ypc9', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4320, '0brwcgnltfmhxipd78aos542', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-04 21:31:23', 'nzy71f5i6g0skwau4lrj3d8b', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4321, 'hwv5j3mqod1gntape8s62yiu', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-04 21:31:27', 'qsz65fvgi8hyx3e7bn14o9wm', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4322, 'vicyxaq4ptu21r8gjs9benmz', 'kqdcn2w40p58r31zyo6efjib', '', '完成了任务 ', 'done', '2019-01-04 21:31:47', '1g3vc8tkyla20fp5rdhxe7mo', 'task', '', 0, NULL, 'check'); +INSERT INTO `pear_project_log` VALUES (4323, 'se0zp1hf792rcmbx8l4dyg5i', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月02日 18:00', 'setEndTime', '2019-01-04 21:32:38', '4cug3e5rodalq9x81ywht0zn', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4325, 'r9qeij67bmxh31na2slkco0z', '6v7be19pwman2fird04gqu53', '', '更新任务优先级为 紧急', 'pri', '2019-01-04 21:33:45', 'zj6skt9orn748gh5mvb2ueif', 'task', '', 0, NULL, 'user'); +INSERT INTO `pear_project_log` VALUES (4326, 'ch920iglvuz6pdreb7wyf4a8', '6v7be19pwman2fird04gqu53', '', '更新截止时间为 01月05日 18:00', 'setEndTime', '2019-01-04 21:33:55', 'krj4p7ix2cf605vyltmudq1e', 'task', '', 0, NULL, 'calendar'); +INSERT INTO `pear_project_log` VALUES (4328, 'dbve2z5hqowgut1c69rai74p', '6v7be19pwman2fird04gqu53', '', '关联了文件 ioncube_loaders_win_nonts_vc11_x86.zip', 'linkFile', '2019-01-11 10:45:40', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'link'); +INSERT INTO `pear_project_log` VALUES (4329, '6lbv9wakfts5jd4eyhgco2ur', '6v7be19pwman2fird04gqu53', '', '关联了文件 技术部项目周报.xlsx', 'linkFile', '2019-01-11 10:45:40', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'link'); +INSERT INTO `pear_project_log` VALUES (4330, 'm6847e9oj20tagqb5uclfyir', '6v7be19pwman2fird04gqu53', '', '关联了文件 cover.png', 'linkFile', '2019-01-11 10:46:07', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'link'); +INSERT INTO `pear_project_log` VALUES (4331, 'cguhle3aypqw05fz6iv4rmjn', '6v7be19pwman2fird04gqu53', '', '取消关联文件 cover.png', 'unlinkFile', '2019-01-11 11:19:16', 'g15scwqm9zxroy7p8bvjt632', 'task', '', 0, NULL, 'disconnect'); +INSERT INTO `pear_project_log` VALUES (4332, 'ibq97pevg4w6tfna3hukydjr', '6v7be19pwman2fird04gqu53', '', '把任务移到了回收站 ', 'recycle', '2019-01-12 22:27:56', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'delete'); +INSERT INTO `pear_project_log` VALUES (4333, 'te280dxhycfkjzvqwi9bam63', '6v7be19pwman2fird04gqu53', '', '恢复了任务 ', 'recovery', '2019-01-13 20:46:29', 'owrs04m3e2klj8uqac6tiy17', 'task', '', 0, NULL, 'undo'); + +-- ---------------------------- +-- Table structure for pear_project_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_member`; +CREATE TABLE `pear_project_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '项目id', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `join_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '加入时间', + `is_owner` int(11) NULL DEFAULT 0 COMMENT '拥有者', + `authorize` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '角色', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 34 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目-成员表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_member +-- ---------------------------- +INSERT INTO `pear_project_member` VALUES (1, 'a8mpr6tvbndk10hj2lwcqzuo', '6v7be19pwman2fird04gqu53', NULL, 1, NULL); +INSERT INTO `pear_project_member` VALUES (2, 'a8mpr6tvbndk10hj2lwcqzuo', 'kqdcn2w40p58r31zyo6efjib', NULL, 0, NULL); +INSERT INTO `pear_project_member` VALUES (6, 'a8mpr6tvbndk10hj2lwcqzuo', 'y680trgedcavbhnz24u7i5m3', '2018-12-23 08:24:29', 0, NULL); +INSERT INTO `pear_project_member` VALUES (7, '8rlqyh56smzpoc1wef7390t2', '6v7be19pwman2fird04gqu53', '2018-12-23 08:25:24', 1, NULL); +INSERT INTO `pear_project_member` VALUES (8, '8rlqyh56smzpoc1wef7390t2', 'kqdcn2w40p58r31zyo6efjib', '2018-12-23 08:25:28', 1, NULL); +INSERT INTO `pear_project_member` VALUES (9, 'nkp4gulsb6oxqyi80fhead39', '6v7be19pwman2fird04gqu53', '2018-12-23 08:26:20', 1, NULL); +INSERT INTO `pear_project_member` VALUES (10, 'sbklfvyouc0qpmwhitn47j5z', '6v7be19pwman2fird04gqu53', '2018-12-23 08:26:24', 1, NULL); +INSERT INTO `pear_project_member` VALUES (11, 'n5opgqevrz1l03h48uwx67d2', '6v7be19pwman2fird04gqu53', '2018-12-23 08:26:31', 1, NULL); +INSERT INTO `pear_project_member` VALUES (12, 'tnxpbov8kez6m4wl2hfjucd9', '6v7be19pwman2fird04gqu53', '2018-12-23 08:31:53', 1, NULL); +INSERT INTO `pear_project_member` VALUES (16, 'mo4uqwfb06dxv8ez2spkl3rg', '6v7be19pwman2fird04gqu53', '2018-12-25 07:20:36', 1, NULL); +INSERT INTO `pear_project_member` VALUES (20, 'mo4uqwfb06dxv8ez2spkl3rg', 'y680trgedcavbhnz24u7i5m3', '2018-12-27 12:04:03', 0, NULL); +INSERT INTO `pear_project_member` VALUES (21, 'ibag9hw3o1tusd5qlpxrk782', 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:02:14', 1, NULL); +INSERT INTO `pear_project_member` VALUES (23, 'p94ckbwv5lyxt2rhzeam3s86', '582', '2019-01-02 11:17:27', 1, NULL); +INSERT INTO `pear_project_member` VALUES (24, '8ulzfth64cd0k1x5peivowm2', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 09:15:11', 1, NULL); +INSERT INTO `pear_project_member` VALUES (25, '8ulzfth64cd0k1x5peivowm2', '6v7be19pwman2fird04gqu53', '2019-01-03 10:51:36', 0, NULL); +INSERT INTO `pear_project_member` VALUES (26, '8ulzfth64cd0k1x5peivowm2', 'y680trgedcavbhnz24u7i5m3', '2019-01-03 10:54:17', 0, NULL); +INSERT INTO `pear_project_member` VALUES (28, 'mo4uqwfb06dxv8ez2spkl3rg', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:29:57', 0, NULL); +INSERT INTO `pear_project_member` VALUES (29, 'elqa703jyvfhpt1dsxkzi8on', 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:17:34', 1, NULL); +INSERT INTO `pear_project_member` VALUES (30, 'elqa703jyvfhpt1dsxkzi8on', 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:17:38', 0, NULL); +INSERT INTO `pear_project_member` VALUES (31, 'ibag9hw3o1tusd5qlpxrk782', '6v7be19pwman2fird04gqu53', '2019-01-04 21:45:41', 0, NULL); +INSERT INTO `pear_project_member` VALUES (32, 'gbim9jpevkh7qr6ufa1t3wl4', 'vys8gd32cfui6brtwzj4pqho', '2019-01-05 21:57:31', 1, NULL); +INSERT INTO `pear_project_member` VALUES (33, 'gbim9jpevkh7qr6ufa1t3wl4', 'kqdcn2w40p58r31zyo6efjib', '2019-01-05 21:57:36', 0, NULL); + +-- ---------------------------- +-- Table structure for pear_project_menu +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_menu`; +CREATE TABLE `pear_project_menu` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `pid` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '父id', + `title` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '名称', + `icon` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '菜单图标', + `url` varchar(400) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '链接', + `file_path` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '文件路径', + `params` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '链接参数', + `node` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '#' COMMENT '权限节点', + `sort` int(11) UNSIGNED NULL DEFAULT 0 COMMENT '菜单排序', + `status` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '状态(0:禁用,1:启用)', + `create_by` bigint(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '创建人', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `is_inner` tinyint(1) NULL DEFAULT 0 COMMENT '是否内页', + `values` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '参数默认值', + `show_slider` tinyint(1) NULL DEFAULT 1 COMMENT '是否显示侧栏', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 163 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目菜单表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_menu +-- ---------------------------- +INSERT INTO `pear_project_menu` VALUES (120, 0, '工作台', 'appstore-o', 'home', 'home', '', '#', 0, 1, 0, '2018-09-30 16:30:01', 0, '', 0); +INSERT INTO `pear_project_menu` VALUES (121, 0, '项目管理', 'project', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (122, 121, '项目列表', 'branches', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (124, 0, '系统设置', 'setting', '#', '#', '', '#', 100, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (125, 124, '成员管理', 'unlock', '#', '#', '', '#', 10, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (126, 125, '账号列表', '', 'system/account', 'system/account', '', 'project/account/index', 10, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (127, 122, '我的组织', '', 'organization', 'organization', '', 'project/organization/index', 30, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (130, 125, '访问授权', '', 'system/account/auth', 'system/account/auth', '', 'project/auth/index', 20, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (131, 125, '授权页面', '', 'system/account/apply', 'system/account/apply', ':id', 'project/auth/apply', 30, 1, 0, '0000-00-00 00:00:00', 1, '', 1); +INSERT INTO `pear_project_menu` VALUES (138, 121, '消息提醒', 'info-circle-o', '#', '#', '', '#', 30, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (139, 138, '站内消息', '', 'notify/notice', 'notify/notice', '', 'project/notify/index', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (140, 138, '系统公告', '', 'notify/system', 'notify/system', '', 'project/notify/index', 10, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (143, 124, '系统管理', 'appstore', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (144, 143, '菜单路由', '', 'system/config/menu', 'system/config/menu', '', 'project/menu/menuadd', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (145, 143, '访问节点', '', 'system/config/node', 'system/config/node', '', 'project/node/save', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (148, 124, '个人管理', 'user', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (149, 148, '个人设置', '', 'account/setting/base', 'account/setting/base', '', 'project/index/editpersonal', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (150, 148, '安全设置', '', 'account/setting/security', 'account/setting/security', '', 'project/index/editpersonal', 0, 1, 0, '0000-00-00 00:00:00', 1, '', 1); +INSERT INTO `pear_project_menu` VALUES (151, 122, '我的项目', '', 'project/list', 'project/list', ':type', 'project/project/index', 0, 1, 0, '0000-00-00 00:00:00', 0, 'my', 1); +INSERT INTO `pear_project_menu` VALUES (152, 122, '回收站', '', 'project/recycle', 'project/recycle', '', 'project/project/index', 20, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (153, 121, '项目空间', 'heat-map', 'project/space/task', 'project/space/task', ':code', '#', 20, 1, 0, '0000-00-00 00:00:00', 1, '', 1); +INSERT INTO `pear_project_menu` VALUES (154, 153, '任务详情', '', 'project/space/task/:code/detail', 'project/space/taskdetail', ':code', 'project/task/read', 0, 1, 0, '0000-00-00 00:00:00', 1, '', 0); +INSERT INTO `pear_project_menu` VALUES (155, 122, '我的收藏', '', 'project/list', 'project/list', ':type', 'project/project/index', 0, 1, 0, '0000-00-00 00:00:00', 0, 'collect', 1); +INSERT INTO `pear_project_menu` VALUES (156, 121, '基础设置', 'experiment', '#', '#', '', '#', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (157, 156, '项目模板', '', 'project/template', 'project/template', '', 'project/project_template/index', 0, 1, 0, '0000-00-00 00:00:00', 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (158, 156, '项目列表模板', '', 'project/template/taskStages', 'project/template/taskStages', ':code', 'project/task_stages_template/index', 0, 1, 0, '0000-00-00 00:00:00', 1, '', 0); +INSERT INTO `pear_project_menu` VALUES (159, 122, '已归档项目', '', 'project/archive', 'project/archive', '', 'project/project/index', 10, 1, 0, NULL, 0, '', 1); +INSERT INTO `pear_project_menu` VALUES (160, 0, '团队成员', 'team', 'members', 'members', '', 'project/account/index', 0, 1, 0, NULL, 0, '', 0); +INSERT INTO `pear_project_menu` VALUES (161, 153, '项目概况', '', 'project/space/overview', 'project/space/overview', ':code', 'project/index/info', 20, 1, 0, NULL, 1, '', 0); +INSERT INTO `pear_project_menu` VALUES (162, 153, '项目文件', '', 'project/space/files', 'project/space/files', ':code', 'project/index/info', 10, 1, 0, NULL, 1, '', 0); + +-- ---------------------------- +-- Table structure for pear_project_node +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_node`; +CREATE TABLE `pear_project_node` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `node` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '节点代码', + `title` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '节点标题', + `is_menu` tinyint(1) UNSIGNED NULL DEFAULT 0 COMMENT '是否可设置为菜单', + `is_auth` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '是否启动RBAC权限控制', + `is_login` tinyint(1) UNSIGNED NULL DEFAULT 1 COMMENT '是否启动登录控制', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_system_node_node`(`node`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 603 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '项目端节点表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_node +-- ---------------------------- +INSERT INTO `pear_project_node` VALUES (360, 'project', '项目管理模块', 0, 1, 1, '2018-09-19 17:48:16'); +INSERT INTO `pear_project_node` VALUES (361, 'project/index/info', '详情', 0, 0, 1, '2018-09-19 17:48:34'); +INSERT INTO `pear_project_node` VALUES (362, 'project/index', '基础版块', 0, 1, 1, '2018-09-19 17:48:34'); +INSERT INTO `pear_project_node` VALUES (363, 'project/index/index', '框架布局', 0, 0, 1, '2018-09-30 16:48:35'); +INSERT INTO `pear_project_node` VALUES (364, 'project/index/systemconfig', '系统信息', 0, 0, 0, '2018-09-30 16:55:11'); +INSERT INTO `pear_project_node` VALUES (365, 'project/index/editpersonal', '修改个人资料', 0, 0, 1, '2018-09-30 17:42:42'); +INSERT INTO `pear_project_node` VALUES (366, 'project/index/uploadavatar', '上传头像', 0, 0, 1, '2018-09-30 17:42:46'); +INSERT INTO `pear_project_node` VALUES (370, 'project/account', '账号管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (371, 'project/account/index', '账号列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (372, 'project/organization/index', '组织列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (373, 'project/organization/save', '创建组织', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (374, 'project/organization/read', '组织信息', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (375, 'project/organization/edit', '编辑组织', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (376, 'project/organization/delete', '删除组织', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (377, 'project/organization', '组织管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (388, 'project/auth/index', '权限列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (389, 'project/auth/add', '添加权限角色', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (390, 'project/auth/edit', '编辑权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (391, 'project/auth/forbid', '禁用权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (392, 'project/auth/resume', '启用权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (393, 'project/auth/del', '删除权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (394, 'project/auth', '访问授权', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (395, 'project/auth/apply', '应用权限', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (396, 'project/notify/index', '通知列表', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (397, 'project/notify/noreads', '未读通知', 0, 0, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (399, 'project/notify/read', '通知信息', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (401, 'project/notify/delete', '删除通知', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (402, 'project/notify', '通知管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (434, 'project/account/auth', '授权管理', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (435, 'project/account/add', '添加账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (436, 'project/account/edit', '编辑账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (437, 'project/account/del', '删除账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (438, 'project/account/forbid', '禁用账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (439, 'project/account/resume', '启用账号', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (498, 'project/notify/setreadied', '设置已读', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (499, 'project/notify/batchdel', '批量删除', 0, 1, 1, '0000-00-00 00:00:00'); +INSERT INTO `pear_project_node` VALUES (500, 'project/auth/setdefault', '设置默认权限', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (501, 'project/department', '部门管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (502, 'project/department/index', '部门列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (503, 'project/department/read', '部门信息', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (504, 'project/department/save', '创建部门', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (505, 'project/department/edit', '编辑部门', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (506, 'project/department/delete', '删除部门', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (507, 'project/department_member', '部门成员管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (508, 'project/department_member/index', '部门成员列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (509, 'project/department_member/searchinvitemember', '搜索部门成员', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (510, 'project/department_member/invitemember', '添加部门成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (511, 'project/department_member/removemember', '移除部门成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (512, 'project/index/changecurrentorganization', '切换当前组织', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (513, 'project/index/editpassword', '修改密码', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (514, 'project/index/uploadimg', '上传图片', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (515, 'project/menu', '菜单管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (516, 'project/menu/menu', '菜单列表', 0, 0, 0, NULL); +INSERT INTO `pear_project_node` VALUES (517, 'project/menu/menuadd', '添加菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (518, 'project/menu/menuedit', '编辑菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (519, 'project/menu/menuforbid', '禁用菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (520, 'project/menu/menuresume', '启用菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (521, 'project/menu/menudel', '删除菜单', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (522, 'project/node', '节点管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (523, 'project/node/index', '节点列表', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (524, 'project/node/alllist', '全部节点列表', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (525, 'project/node/clear', '清理节点', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (526, 'project/node/save', '编辑节点', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (527, 'project/project', '项目管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (528, 'project/project/index', '项目列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (529, 'project/project/selflist', '个人项目列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (530, 'project/project/save', '创建项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (531, 'project/project/read', '项目信息', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (532, 'project/project/edit', '编辑项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (533, 'project/project/uploadcover', '上传项目封面', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (534, 'project/project/recycle', '项目放入回收站', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (535, 'project/project/recovery', '恢复项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (536, 'project/project/archive', '归档项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (537, 'project/project/recoveryarchive', '取消归档项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (538, 'project/project/quit', '退出项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (539, 'project/project_collect', '项目收藏管理', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (540, 'project/project_collect/collect', '收藏项目', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (541, 'project/project_member', '项目成员管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (542, 'project/project_member/index', '项目成员列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (543, 'project/project_member/searchinvitemember', '搜索项目成员', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (544, 'project/project_member/invitemember', '邀请项目成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (545, 'project/project_template', '项目模板管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (546, 'project/project_template/index', '项目模板列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (547, 'project/project_template/save', '创建项目模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (548, 'project/project_template/uploadcover', '上传项目模板封面', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (549, 'project/project_template/edit', '编辑项目模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (550, 'project/project_template/delete', '删除项目模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (551, 'project/task/index', '任务列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (552, 'project/task/selflist', '个人任务列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (553, 'project/task/read', '任务信息', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (554, 'project/task/save', '创建任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (555, 'project/task/taskdone', '更改任务状态', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (556, 'project/task/assigntask', '指派任务执行者', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (557, 'project/task/sort', '任务排序', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (558, 'project/task/createcomment', '发表任务评论', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (559, 'project/task/edit', '编辑任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (560, 'project/task/like', '点赞任务', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (561, 'project/task/star', '收藏任务', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (562, 'project/task/recycle', '移动任务到回收站', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (563, 'project/task/recovery', '恢复任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (564, 'project/task/delete', '删除任务', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (565, 'project/task', '任务管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (569, 'project/task_member/index', '任务成员列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (570, 'project/task_member/searchinvitemember', '搜索任务成员', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (571, 'project/task_member/invitemember', '添加任务成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (572, 'project/task_member/invitememberbatch', '批量添加任务成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (573, 'project/task_member', '任务成员管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (574, 'project/task_stages', '任务分组管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (575, 'project/task_stages/index', '任务分组列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (576, 'project/task_stages/tasks', '任务分组任务列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (577, 'project/task_stages/sort', '任务分组排序', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (578, 'project/task_stages/save', '添加任务分组', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (579, 'project/task_stages/edit', '编辑任务分组', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (580, 'project/task_stages/delete', '删除任务分组', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (581, 'project/task_stages_template/index', '任务分组模板列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (582, 'project/task_stages_template/save', '创建任务分组模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (583, 'project/task_stages_template/edit', '编辑任务分组模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (584, 'project/task_stages_template/delete', '删除任务分组模板', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (585, 'project/task_stages_template', '任务分组模板管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (587, 'project/project_member/removemember', '移除项目成员', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (588, 'project/task/datetotalforproject', '任务统计', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (589, 'project/task/tasksources', '任务资源列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (590, 'project/file', '文件管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (591, 'project/file/index', '文件列表', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (592, 'project/file/read', '文件详情', 0, 0, 1, NULL); +INSERT INTO `pear_project_node` VALUES (593, 'project/file/uploadfiles', '上传文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (594, 'project/file/edit', '编辑文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (595, 'project/file/recycle', '文件移至回收站', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (596, 'project/file/recovery', '恢复文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (597, 'project/file/delete', '删除文件', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (598, 'project/project/getlogbyselfproject', '项目概况', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (599, 'project/source_link', '资源关联管理', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (600, 'project/source_link/delete', '取消关联', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (601, 'project/task/tasklog', '任务动态', 0, 1, 1, NULL); +INSERT INTO `pear_project_node` VALUES (602, 'project/task/recyclebatch', '批量移动任务到回收站', 0, 1, 1, NULL); + +-- ---------------------------- +-- Table structure for pear_project_template +-- ---------------------------- +DROP TABLE IF EXISTS `pear_project_template`; +CREATE TABLE `pear_project_template` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `sort` tinyint(2) NULL DEFAULT 0, + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `organization_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '组织id', + `cover` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '封面', + `member_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '创建人', + `is_system` tinyint(1) NULL DEFAULT 0 COMMENT '系统默认', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 20 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '项目类型表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_project_template +-- ---------------------------- +INSERT INTO `pear_project_template` VALUES (11, '产品进展', '适用于互联网产品人员对产品计划、跟进及发布管理', 0, '2018-04-30 22:15:10', 'd85f1bvwpml2nhxe94zu7tyi', '6v7be19pwman2fird04gqu53', 'http://easyproject.net/static/image/default/cover.png', '', 1); +INSERT INTO `pear_project_template` VALUES (12, '需求管理', '适用于产品部门对需求的收集、评估及反馈管理', 0, '2018-04-30 22:16:29', 'd85f1bvwpml2nhxe92zu7tyi', '6v7be19pwman2fird04gqu53', 'http://easyproject.net/static/image/default/cover.png', '', 1); +INSERT INTO `pear_project_template` VALUES (13, '机械制造', '适用于制造商对图纸设计及制造安装的工作流程管理', 0, '2018-04-30 22:19:06', 'd85f1bvwpml2nhxe91zu7tyi', '6v7be19pwman2fird04gqu53', 'http://easyproject.net/static/image/default/cover.png', '', 1); +INSERT INTO `pear_project_template` VALUES (19, 'OKR 管理', '适用于团队的 OKR 管理', 0, '2018-12-24 16:57:49', 'un6125mxt4dcizhjqwvgyb3a', '6v7be19pwman2fird04gqu53', 'https://beta.vilson.xyz/static/upload//20190103/4c46f35da98ca0e1eeed192d8576b9c4.png', '6v7be19pwman2fird04gqu53', 0); + +-- ---------------------------- +-- Table structure for pear_source_link +-- ---------------------------- +DROP TABLE IF EXISTS `pear_source_link`; +CREATE TABLE `pear_source_link` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `source_type` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源类型', + `source_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '资源编号', + `link_type` char(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联类型', + `link_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '关联编号', + `organization_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '组织编码', + `create_by` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人', + `create_time` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '资源关联表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_source_link +-- ---------------------------- +INSERT INTO `pear_source_link` VALUES (4, '47eu1kg32wrdb9inq8zj5xas', 'file', 'lhp9dfz831jquoam6g4nbery', 'task', 'g15scwqm9zxroy7p8bvjt632', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0); +INSERT INTO `pear_source_link` VALUES (5, 'bkh7y02dinz9g3wuet1asr6x', 'file', 'lr08qzj5bucy2p1osinhkdef', 'task', 'g15scwqm9zxroy7p8bvjt632', '6v7be19pwman2fird04gqu53', '6v7be19pwman2fird04gqu53', '2019-01-11 10:45:40', 0); + +-- ---------------------------- +-- Table structure for pear_system_config +-- ---------------------------- +DROP TABLE IF EXISTS `pear_system_config`; +CREATE TABLE `pear_system_config` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `name` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '配置编码', + `value` varchar(500) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '配置值', + PRIMARY KEY (`id`) USING BTREE, + INDEX `index_system_config_name`(`name`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 43 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统参数配置' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_system_config +-- ---------------------------- +INSERT INTO `pear_system_config` VALUES (1, 'app_name', 'Pear Project'); +INSERT INTO `pear_system_config` VALUES (2, 'site_name', 'Pear Project'); +INSERT INTO `pear_system_config` VALUES (3, 'app_version', '2.0'); +INSERT INTO `pear_system_config` VALUES (4, 'site_copy', 'Copyright © 2018 Pear Project出品'); +INSERT INTO `pear_system_config` VALUES (5, 'browser_icon', ''); +INSERT INTO `pear_system_config` VALUES (6, 'tongji_baidu_key', ''); +INSERT INTO `pear_system_config` VALUES (7, 'miitbeian', '粤ICP备16eeeee2号-2'); +INSERT INTO `pear_system_config` VALUES (8, 'storage_type', 'local'); +INSERT INTO `pear_system_config` VALUES (9, 'storage_local_exts', 'png,jpg,rar,doc,icon,mp4,zip,gif,jpeg,bmp,webp,mp4,m3u8,rmvb,avi,swf,3gp,mkv,flv,txt,docx,pages,epub,pdf,numbers,csv,xls,xlsx,keynote,ppt,pptx,mp3,wav,wma,ogg,aac,flac'); +INSERT INTO `pear_system_config` VALUES (10, 'storage_qiniu_bucket', ''); +INSERT INTO `pear_system_config` VALUES (11, 'storage_qiniu_domain', ''); +INSERT INTO `pear_system_config` VALUES (12, 'storage_qiniu_access_key', ''); +INSERT INTO `pear_system_config` VALUES (13, 'storage_qiniu_secret_key', ''); +INSERT INTO `pear_system_config` VALUES (14, 'storage_oss_bucket', 'cuci'); +INSERT INTO `pear_system_config` VALUES (15, 'storage_oss_endpoint', ''); +INSERT INTO `pear_system_config` VALUES (16, 'storage_oss_domain', ''); +INSERT INTO `pear_system_config` VALUES (17, 'storage_oss_keyid', ''); +INSERT INTO `pear_system_config` VALUES (18, 'storage_oss_secret', ''); +INSERT INTO `pear_system_config` VALUES (34, 'wechat_appid', ''); +INSERT INTO `pear_system_config` VALUES (35, 'wechat_appkey', ''); +INSERT INTO `pear_system_config` VALUES (36, 'storage_oss_is_https', 'http'); +INSERT INTO `pear_system_config` VALUES (37, 'wechat_type', 'thr'); +INSERT INTO `pear_system_config` VALUES (38, 'wechat_token', 'test'); +INSERT INTO `pear_system_config` VALUES (39, 'wechat_appsecret', ''); +INSERT INTO `pear_system_config` VALUES (40, 'wechat_encodingaeskey', ''); +INSERT INTO `pear_system_config` VALUES (41, 'wechat_thr_appid', ''); +INSERT INTO `pear_system_config` VALUES (42, 'wechat_thr_appkey', ''); + +-- ---------------------------- +-- Table structure for pear_system_log +-- ---------------------------- +DROP TABLE IF EXISTS `pear_system_log`; +CREATE TABLE `pear_system_log` ( + `id` bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT, + `ip` char(15) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '操作者IP地址', + `node` char(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '当前操作节点', + `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '操作人用户名', + `action` varchar(200) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '操作行为', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '操作内容描述', + `create_at` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '系统操作日志表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Table structure for pear_task +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task`; +CREATE TABLE `pear_task` ( + `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '编号', + `project_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '项目编号', + `name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pri` tinyint(3) UNSIGNED NULL DEFAULT 0 COMMENT '紧急程度', + `execute_status` enum('wait','doing','done','pause','cancel','closed') CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT 'wait' COMMENT '执行状态', + `description` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '详情', + `create_by` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建人', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建日期', + `assign_to` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '指派给谁', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '回收站', + `stage_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '任务列表', + `task_tag` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '任务标签', + `done` tinyint(2) NULL DEFAULT 0 COMMENT '是否完成', + `begin_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '开始时间', + `end_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '截止时间', + `remind_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '提醒时间', + `pcode` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '父任务id', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `like` int(7) NULL DEFAULT 0 COMMENT '点赞数', + `star` int(7) NULL DEFAULT 0 COMMENT '收藏数', + `deleted_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '删除时间', + `private` tinyint(1) NULL DEFAULT 0 COMMENT '是否隐私模式', + `id_num` int(7) NULL DEFAULT 1 COMMENT '任务id编号', + PRIMARY KEY (`id`, `project_code`) USING BTREE, + INDEX `task`(`code`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 12356 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_task +-- ---------------------------- +INSERT INTO `pear_task` VALUES (12182, 'mv4usefb06dx33ez2spkl223', 'mo4uqwfb06dxv8ez2spkl3rg', '排序样式', 0, 'wait', '', 'kqdcn2w40p58r31zyo6efjib', '2018-07-14 13:58:36', '', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '12173', 12182, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12183, 'mv4usefb06dxv8ez2spkl223', 'mo4uqwfb06dxv8ez2spkl3rg', 'Notification 组件升级 rc-notification 到 3.3.0', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2018-11-02 13:51:48', 'kqdcn2w40p58r31zyo6efjib', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, '2019-01-08 18:00', NULL, '', 1, 3, 1, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12208, 'aut9wrz1pn0elf5s47ivx26o', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Badge 代码错误引起的 TypeScript 类型报错', 2, 'wait', '

这里是备注内容


', '6v7be19pwman2fird04gqu53', '2018-12-25 16:13:34', 'y680trgedcavbhnz24u7i5m3', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 1, NULL, '', NULL, '', 0, 3, 1, NULL, 0, 3); +INSERT INTO `pear_task` VALUES (12218, '0tjma1un2gz8rf4ywo7c6de9', 'ibag9hw3o1tusd5qlpxrk782', '阅读「分享」中的使用案例,为新产品发布计划建立一个公示板吧!', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:17', '6v7be19pwman2fird04gqu53', 0, 'ipzyscgfo5l1qvah2xm4638t', NULL, 0, NULL, NULL, NULL, '', 12218, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12219, 'xkqg60sld15fcphwt4ya3rb8', 'ibag9hw3o1tusd5qlpxrk782', '编写文档', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:22', '6v7be19pwman2fird04gqu53', 0, 'ipzyscgfo5l1qvah2xm4638t', NULL, 1, NULL, NULL, NULL, '', 12219, 0, 0, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12223, '4mtnhwbe0gjdkaur2ic7xsv6', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 Table 动态设置表头分组报错的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:02:28', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12223, 0, 0, NULL, 0, 4); +INSERT INTO `pear_task` VALUES (12224, 'hj5s73zk6amd9wfvbxoygpic', 'mo4uqwfb06dxv8ez2spkl3rg', '新增阿拉伯语', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:12:06', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12224, 0, 0, NULL, 0, 5); +INSERT INTO `pear_task` VALUES (12225, 'l027b1dyrv93zu4ewmtoa6q5', 'mo4uqwfb06dxv8ez2spkl3rg', 'Table 支持 slot-scope 用法', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:14:09', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12225, 0, 0, NULL, 0, 6); +INSERT INTO `pear_task` VALUES (12226, 'rqb7vi254tna3uzhdgo0f6ey', 'mo4uqwfb06dxv8ez2spkl3rg', 'Table 新增取消全选事件 @on-select-all-cancel', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-28 13:15:16', '', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 12226, 0, 0, NULL, 0, 7); +INSERT INTO `pear_task` VALUES (12287, 'qscug70y98zpk6edbnf3livr', 'ibag9hw3o1tusd5qlpxrk782', '1', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:43', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, '2018-12-31 18:00', NULL, '', 0, 1, 0, NULL, 0, 3); +INSERT INTO `pear_task` VALUES (12288, 'rzpu5cxl63fvb2y8gwdnsjqk', 'ibag9hw3o1tusd5qlpxrk782', '2', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:44', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 4); +INSERT INTO `pear_task` VALUES (12289, 'ozi8awms1lpcbde4fuq5ktgj', 'ibag9hw3o1tusd5qlpxrk782', '3', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:45', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 5); +INSERT INTO `pear_task` VALUES (12290, 'xejt6431q8ly97bkid5z2pun', 'ibag9hw3o1tusd5qlpxrk782', '4', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:46', 'y680trgedcavbhnz24u7i5m3', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 6); +INSERT INTO `pear_task` VALUES (12291, 'zkqb6if5ogdts27lx13r4yju', 'ibag9hw3o1tusd5qlpxrk782', '5', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:47', '6v7be19pwman2fird04gqu53', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 7); +INSERT INTO `pear_task` VALUES (12292, '9wsohy8jgapl6x2iutbm7k34', 'ibag9hw3o1tusd5qlpxrk782', '6', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:49', '', 0, '3uz8afjkxnogwivd9s0lqp7y', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 8); +INSERT INTO `pear_task` VALUES (12295, 'q9y6ksvtifwpuhna0e32jgm1', '8ulzfth64cd0k1x5peivowm2', '1', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:04', 'kqdcn2w40p58r31zyo6efjib', 0, 'pfi2ltmjhxuda90ncsgb5vwo', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12296, 'wyklgmhpt5qr47x3zsf9nibj', '8ulzfth64cd0k1x5peivowm2', '2', 0, 'wait', '

66

', 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:13', '6v7be19pwman2fird04gqu53', 0, 'ht0gfnevaq7kp3ldx16i82yj', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12297, 'm6cloqrbh7tf0wg1jsvp9nay', 'mo4uqwfb06dxv8ez2spkl3rg', '99', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 11:00:15', 'y680trgedcavbhnz24u7i5m3', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 1, NULL, NULL, NULL, 'aut9wrz1pn0elf5s47ivx26o', 0, 0, 0, NULL, 0, 8); +INSERT INTO `pear_task` VALUES (12298, 'p1aujdigrlxky76h8cs3z4w0', 'mo4uqwfb06dxv8ez2spkl3rg', '增加了一个新组件 Comment', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:29', 'y680trgedcavbhnz24u7i5m3', 0, 'jvyswuxz34qk2cpt9o7ldb60', NULL, 0, NULL, '2019-01-01 18:00', NULL, '', 0, 0, 0, NULL, 0, 9); +INSERT INTO `pear_task` VALUES (12299, '2bn918l6ejyzousa73dkpgci', 'mo4uqwfb06dxv8ez2spkl3rg', '增加了一个新组件 ConfigProvider 为组件提供统一的全局化配置', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:37', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 10); +INSERT INTO `pear_task` VALUES (12300, '3qz5hfsin69xt8cgbd70lkew', 'mo4uqwfb06dxv8ez2spkl3rg', 'Avatar 组件增加 srcSet 属性,用于设置图片类头像响应式资源地址', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:45', 'kqdcn2w40p58r31zyo6efjib', 0, 'jvyswuxz34qk2cpt9o7ldb60', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 11); +INSERT INTO `pear_task` VALUES (12301, 'xkic58d20srnu9jm7ohqw14f', 'mo4uqwfb06dxv8ez2spkl3rg', '增加 less 变量 @font-variant-base 定制 font-variant 样式', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:53', 'kqdcn2w40p58r31zyo6efjib', 0, 'jvyswuxz34qk2cpt9o7ldb60', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 12); +INSERT INTO `pear_task` VALUES (12302, '6hj43ueim2bk187sqzcoy59v', 'mo4uqwfb06dxv8ez2spkl3rg', '优化鼠标悬停在可排序的表头上时 title 的显示', 2, 'wait', '

这里是备注内容


', '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:01', 'y680trgedcavbhnz24u7i5m3', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, '2019-01-06 18:00', NULL, '', 1, 1, 0, NULL, 0, 13); +INSERT INTO `pear_task` VALUES (12303, 'twb8f52jasn9vry6iko0dqg4', 'mo4uqwfb06dxv8ez2spkl3rg', '修正 Comment author 属性的类型为 ReactNode', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:09', '', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 0, 0, 1, NULL, 0, 14); +INSERT INTO `pear_task` VALUES (12304, 'gjmotpbrwva079ukde4izn38', 'mo4uqwfb06dxv8ez2spkl3rg', '优化 Spin 样式并略微提升了切换状态的性能', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:16', '6v7be19pwman2fird04gqu53', 0, 'p56enm7zck4id2rb0tx9lguh', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 15); +INSERT INTO `pear_task` VALUES (12305, 'uwq87z2f0hnvrl6o9gtcb3iy', 'mo4uqwfb06dxv8ez2spkl3rg', '微调 Card 头部和加载中的样式细节', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:21', 'y680trgedcavbhnz24u7i5m3', 0, 'p56enm7zck4id2rb0tx9lguh', NULL, 0, NULL, NULL, NULL, '', 0, 0, 2, NULL, 0, 16); +INSERT INTO `pear_task` VALUES (12306, 'qug5e4alndm7930ipxwyvc2h', 'mo4uqwfb06dxv8ez2spkl3rg', 'Cascader 升级 rc-calendar', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:04', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, '', 3, 0, 0, NULL, 0, 17); +INSERT INTO `pear_task` VALUES (12307, 'yctbsv81x6dmahkf7ei5o4r9', 'mo4uqwfb06dxv8ez2spkl3rg', 'Upload 组件升级 rc-upload 到 2.5.0', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:17', 'kqdcn2w40p58r31zyo6efjib', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, '', 4, 0, 0, NULL, 0, 18); +INSERT INTO `pear_task` VALUES (12308, 'm7u8fdp41cwrtkjxyzq2ion3', 'mo4uqwfb06dxv8ez2spkl3rg', '重构 Tag 组件,简化代码并提升性能', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:30', 'kqdcn2w40p58r31zyo6efjib', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 19); +INSERT INTO `pear_task` VALUES (12309, 'jo0i8fq2579kbdgsmcw1nev4', 'mo4uqwfb06dxv8ez2spkl3rg', 'Badge 进行了重构,count 支持自定义组件', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:36', '6v7be19pwman2fird04gqu53', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 3, 0, 0, NULL, 0, 20); +INSERT INTO `pear_task` VALUES (12310, 'owrs04m3e2klj8uqac6tiy17', 'mo4uqwfb06dxv8ez2spkl3rg', '重构了 Tree 底层的代码,以解决一些存在了很久的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:54', 'y680trgedcavbhnz24u7i5m3', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, '2019-01-12 22:27:56', 0, 21); +INSERT INTO `pear_task` VALUES (12311, 'g15scwqm9zxroy7p8bvjt632', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Divider 与浮动元素一起使用时的样式问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:21', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 22); +INSERT INTO `pear_task` VALUES (12312, '0a84xkg12enqjml7rz6dbifw', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Form 高级搜索模式下的样式问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:25', 'kqdcn2w40p58r31zyo6efjib', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, '2019-01-04 18:00', NULL, '', 0, 0, 0, NULL, 0, 23); +INSERT INTO `pear_task` VALUES (12313, 'fax4gez2jlk15tvsu3dc6p98', 'mo4uqwfb06dxv8ez2spkl3rg', '修复了 Upload 对无扩展名图片地址的预览展示问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:29', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 24); +INSERT INTO `pear_task` VALUES (12314, 'zv4hx1ugpn98be5skc3wym72', 'mo4uqwfb06dxv8ez2spkl3rg', '修复一处 less 语法错误', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:33', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 25); +INSERT INTO `pear_task` VALUES (12315, 'jiy25eobh1cnp7ruvg9d0m6s', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 LocaleProvider 中 moment.locale 调用报错的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:39', '', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 26); +INSERT INTO `pear_task` VALUES (12316, '4pv9brqnm0cigwu5f3zeyxdk', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 WeekPicker 的 style 属性不生效的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:43', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 27); +INSERT INTO `pear_task` VALUES (12317, 'td1qznl9ms65gbcfej0k4vup', 'mo4uqwfb06dxv8ez2spkl3rg', 'Carousel: 升级 react-slick 版本以修复宽度计算错误', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:50', 'y680trgedcavbhnz24u7i5m3', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 28); +INSERT INTO `pear_task` VALUES (12318, 'fkrsvpzmj8xyo045hiugqt92', 'mo4uqwfb06dxv8ez2spkl3rg', '修复 enterButton 的值为 button 元素时显示错误的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:56', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 29); +INSERT INTO `pear_task` VALUES (12319, '0b6wlc3754fr8gdvupx9aoys', 'mo4uqwfb06dxv8ez2spkl3rg', '修复表单校验文字消失的时候输入框会抖一下的问题', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:02', '6v7be19pwman2fird04gqu53', 0, '7z8tgb6xevy2aj9nui5fk0w1', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 30); +INSERT INTO `pear_task` VALUES (12320, 'bl1t7xjwpi9m2aocnsz83fk6', 'mo4uqwfb06dxv8ez2spkl3rg', '重构了 DatePicker 相关 type 定义', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:18', 'y680trgedcavbhnz24u7i5m3', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 31); +INSERT INTO `pear_task` VALUES (12321, 'hxntygarp3094c7w1856iujm', 'mo4uqwfb06dxv8ez2spkl3rg', 'Steps 进行了重构,首次渲染的时候不会再闪烁', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:24', '6v7be19pwman2fird04gqu53', 0, 'g0yw3r54qahbk7lets6fv2on', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 32); +INSERT INTO `pear_task` VALUES (12322, 'gk8ipqm5406br7cwd9l1zefs', 'mo4uqwfb06dxv8ez2spkl3rg', 'Change hover over message of Column', 0, 'wait', NULL, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:09:28', 'y680trgedcavbhnz24u7i5m3', 0, 'p56enm7zck4id2rb0tx9lguh', NULL, 0, NULL, NULL, NULL, '6hj43ueim2bk187sqzcoy59v', 0, 0, 0, NULL, 0, 33); +INSERT INTO `pear_task` VALUES (12323, 'o61b3s24exmcy8njkparwthd', 'mo4uqwfb06dxv8ez2spkl3rg', '新增 directory 属性,支持上传一个文件夹', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:18', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'yctbsv81x6dmahkf7ei5o4r9', 0, 0, 0, NULL, 0, 34); +INSERT INTO `pear_task` VALUES (12324, 'orycwlhf7n2qx1pta038dzjk', 'mo4uqwfb06dxv8ez2spkl3rg', 'action 属性支持作为一个返回 Promise 对象的函数,使用更加灵活', 0, 'wait', NULL, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:24', '6v7be19pwman2fird04gqu53', 0, 'psemnf3ugo89vc5r2hkxid1t', NULL, 0, NULL, NULL, NULL, 'yctbsv81x6dmahkf7ei5o4r9', 0, 0, 0, NULL, 0, 35); +INSERT INTO `pear_task` VALUES (12325, 'up6hn9bd34c8mglwaj1ytefz', 'elqa703jyvfhpt1dsxkzi8on', 'Add variant prop and deprecate fullWidth and scrollable props', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:13', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 1); +INSERT INTO `pear_task` VALUES (12326, 'krj4p7ix2cf605vyltmudq1e', 'elqa703jyvfhpt1dsxkzi8on', 'Add styles to make size property work with extended property', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:19', '', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, '2019-01-05 18:00', NULL, '', 0, 0, 0, NULL, 0, 2); +INSERT INTO `pear_task` VALUES (12327, '1g3vc8tkyla20fp5rdhxe7mo', 'elqa703jyvfhpt1dsxkzi8on', 'Add cross references from Modal docs to other components', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:46', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 3); +INSERT INTO `pear_task` VALUES (12328, 'nqrleu2c90zsdaj1yph4m8bt', 'elqa703jyvfhpt1dsxkzi8on', 'Add createSvgIcon type definition', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:25', 'kqdcn2w40p58r31zyo6efjib', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 4); +INSERT INTO `pear_task` VALUES (12329, 'mix3cg2eh1u60fknd7yz9v5t', 'elqa703jyvfhpt1dsxkzi8on', 'Add customized demo', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:37', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 5); +INSERT INTO `pear_task` VALUES (12330, 'dckxz1vpujtafshgr20mwo7e', 'elqa703jyvfhpt1dsxkzi8on', 'Add defaultTheme option for makeStyles', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:45', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 6); +INSERT INTO `pear_task` VALUES (12331, 'fd1avskez2q43w80xhb7ypc9', 'elqa703jyvfhpt1dsxkzi8on', 'Add nextjs-hooks-with-typescript', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:53', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 7); +INSERT INTO `pear_task` VALUES (12332, 'as2y4r6mwxuhgvncop3f8z90', 'elqa703jyvfhpt1dsxkzi8on', 'Add note on archived components', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:01', 'y680trgedcavbhnz24u7i5m3', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 8); +INSERT INTO `pear_task` VALUES (12333, '8zj3vpx0b7qud24ylfgces1m', 'elqa703jyvfhpt1dsxkzi8on', 'Add Instagram theme', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:08', 'kqdcn2w40p58r31zyo6efjib', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 9); +INSERT INTO `pear_task` VALUES (12334, 'hcrdvbuzwgojst2f0p134qxi', 'elqa703jyvfhpt1dsxkzi8on', 'Add component prop', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:18', 'kqdcn2w40p58r31zyo6efjib', 0, '2sf7h3p01l5qgdeumrzny4bi', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 10); +INSERT INTO `pear_task` VALUES (12335, 'lmognshqz21dbewcu9a3rx87', 'elqa703jyvfhpt1dsxkzi8on', 'Fix utils.chainPropTypes issue', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:37', 'kqdcn2w40p58r31zyo6efjib', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 11); +INSERT INTO `pear_task` VALUES (12336, 'n6ulc7ebxpqahi50dy9k1sgf', 'elqa703jyvfhpt1dsxkzi8on', 'Fix vertical text alignment by reducing padding', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:51', '', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 12); +INSERT INTO `pear_task` VALUES (12337, 'rqjng1kfcp4wyiamt6o23zbu', 'elqa703jyvfhpt1dsxkzi8on', 'Fix infinite loop in the scroll button logic', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:57', 'y680trgedcavbhnz24u7i5m3', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 13); +INSERT INTO `pear_task` VALUES (12338, 'qsz65fvgi8hyx3e7bn14o9wm', 'elqa703jyvfhpt1dsxkzi8on', 'Fix component animations', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:05', 'kqdcn2w40p58r31zyo6efjib', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 14); +INSERT INTO `pear_task` VALUES (12339, 'byiuxhn0v6sod4zap1t2fclr', 'elqa703jyvfhpt1dsxkzi8on', 'Fix responsivePropType typo', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:12', 'kqdcn2w40p58r31zyo6efjib', 0, 'njd4er1ohakl6bz258qcfgsv', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 15); +INSERT INTO `pear_task` VALUES (12340, 'jxd3rpmay6qonsk1i8wg5e9u', 'elqa703jyvfhpt1dsxkzi8on', 'Change action element to have a fixed right margin', 2, 'wait', '

This is the content


', '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:27', 'kqdcn2w40p58r31zyo6efjib', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, '2019-01-09 18:00', NULL, '', 0, 0, 0, NULL, 0, 16); +INSERT INTO `pear_task` VALUES (12341, 'vmzeciodgbfp7ysu38tq10kj', 'elqa703jyvfhpt1dsxkzi8on', 'Change height from 5 to 4 pixels', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:33', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 17); +INSERT INTO `pear_task` VALUES (12342, '6cagd725tifonvw0qphe9zsb', 'elqa703jyvfhpt1dsxkzi8on', 'Change sub-components to have fixed gutters', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:45', 'kqdcn2w40p58r31zyo6efjib', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 18); +INSERT INTO `pear_task` VALUES (12343, 'xu3jgyow2s9f1km0rctqin4v', 'elqa703jyvfhpt1dsxkzi8on', 'Change the classes structure to match the core components convention', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:54', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 19); +INSERT INTO `pear_task` VALUES (12344, 'k3g07m1qyctvbp95siohju6f', 'elqa703jyvfhpt1dsxkzi8on', 'Update the action spacing to better match the spec', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:12', 'y680trgedcavbhnz24u7i5m3', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 1, 3, 0, NULL, 0, 20); +INSERT INTO `pear_task` VALUES (12345, 'oh5wpj9kd8e6ltusxq271ma3', 'elqa703jyvfhpt1dsxkzi8on', 'Update the emotion documentation', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:18', 'y680trgedcavbhnz24u7i5m3', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 2, 0, 0, NULL, 0, 21); +INSERT INTO `pear_task` VALUES (12346, 'akdwslbtp3z82xecui0y4ovq', 'elqa703jyvfhpt1dsxkzi8on', 'Update the CodeFund embed script', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:25', 'y680trgedcavbhnz24u7i5m3', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 3, 0, 0, NULL, 0, 22); +INSERT INTO `pear_task` VALUES (12347, 'hayfr6vl398nq5exgszobu2j', 'elqa703jyvfhpt1dsxkzi8on', 'Update react-select demo to have isClearable set to true', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:31', '', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 4, 0, 0, NULL, 0, 23); +INSERT INTO `pear_task` VALUES (12348, 'mf80iu15kepavbg2r9ldcjsh', 'elqa703jyvfhpt1dsxkzi8on', 'Update album page-layout preview image album.png', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:41', 'kqdcn2w40p58r31zyo6efjib', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 0, NULL, NULL, NULL, '', 5, 0, 0, NULL, 0, 24); +INSERT INTO `pear_task` VALUES (12349, 'nzy71f5i6g0skwau4lrj3d8b', 'elqa703jyvfhpt1dsxkzi8on', 'Update some components to better match the Material specification', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:08', 'kqdcn2w40p58r31zyo6efjib', 0, 'sft603lxe5phk89ou1cgmiby', NULL, 1, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 25); +INSERT INTO `pear_task` VALUES (12350, '4cug3e5rodalq9x81ywht0zn', 'elqa703jyvfhpt1dsxkzi8on', 'Remove hoisting of static properties in HOCs', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:20', 'kqdcn2w40p58r31zyo6efjib', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, '2018-12-31 18:00', NULL, '', 0, 0, 0, NULL, 0, 26); +INSERT INTO `pear_task` VALUES (12351, '92fow0le47htb6xkv5ynzuri', 'elqa703jyvfhpt1dsxkzi8on', 'Remove the withRoot HOC', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:24', 'kqdcn2w40p58r31zyo6efjib', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 27); +INSERT INTO `pear_task` VALUES (12352, '6ky18i9cg0eqvfzn2th3ux5l', 'elqa703jyvfhpt1dsxkzi8on', '100% remove the prop types', 0, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:34', '', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 28); +INSERT INTO `pear_task` VALUES (12353, 'zj6skt9orn748gh5mvb2ueif', 'elqa703jyvfhpt1dsxkzi8on', 'Remove unused lint directives', 1, 'wait', NULL, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:40', 'y680trgedcavbhnz24u7i5m3', 0, '0jmqucy41h3rt9ag27wils6b', NULL, 0, NULL, NULL, NULL, '', 0, 0, 0, NULL, 0, 29); +INSERT INTO `pear_task` VALUES (12354, 'a75dcqx2sjivokmg49yh380l', 'elqa703jyvfhpt1dsxkzi8on', 'Improve demos loading', 0, 'wait', NULL, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:13', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, 'jxd3rpmay6qonsk1i8wg5e9u', 0, 0, 0, NULL, 0, 30); +INSERT INTO `pear_task` VALUES (12355, '7ns924ofulpjxkgq06y3bm5r', 'elqa703jyvfhpt1dsxkzi8on', 'Improve the service-worker logic', 0, 'wait', NULL, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:19', 'y680trgedcavbhnz24u7i5m3', 0, 'oxcj9krmqeu08wbga2ftz7ls', NULL, 0, NULL, NULL, NULL, 'jxd3rpmay6qonsk1i8wg5e9u', 0, 0, 0, NULL, 0, 31); + +-- ---------------------------- +-- Table structure for pear_task_like +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_like`; +CREATE TABLE `pear_task_like` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务ID', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 117 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务点赞表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_task_like +-- ---------------------------- +INSERT INTO `pear_task_like` VALUES (91, 'n156qrj4l8720xhvioefmzys', '6v7be19pwman2fird04gqu53', '2018-12-28 20:37:49'); +INSERT INTO `pear_task_like` VALUES (100, 'caq96fw7hnsv1pude2mibxz8', '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:32'); +INSERT INTO `pear_task_like` VALUES (101, 'j6xkdynh4c2sm1pblvztaweg', '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:35'); +INSERT INTO `pear_task_like` VALUES (102, '4fua38vpqgk706csx2lb9etj', '6v7be19pwman2fird04gqu53', '2018-12-29 13:07:20'); +INSERT INTO `pear_task_like` VALUES (105, 'aut9wrz1pn0elf5s47ivx26o', '6v7be19pwman2fird04gqu53', '2018-12-30 21:51:39'); +INSERT INTO `pear_task_like` VALUES (108, 'mv4usefb06dxv8ez2spkl223', '6v7be19pwman2fird04gqu53', '2018-12-31 14:08:27'); +INSERT INTO `pear_task_like` VALUES (109, 'qscug70y98zpk6edbnf3livr', '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:57'); +INSERT INTO `pear_task_like` VALUES (111, 'mv4usefb06dxv8ez2spkl223', 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:20:34'); +INSERT INTO `pear_task_like` VALUES (112, 'mv4usefb06dxv8ez2spkl223', 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:20:43'); +INSERT INTO `pear_task_like` VALUES (113, 'k3g07m1qyctvbp95siohju6f', 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:32:09'); +INSERT INTO `pear_task_like` VALUES (114, 'k3g07m1qyctvbp95siohju6f', 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:32:12'); +INSERT INTO `pear_task_like` VALUES (115, 'k3g07m1qyctvbp95siohju6f', '6v7be19pwman2fird04gqu53', '2019-01-04 21:32:23'); +INSERT INTO `pear_task_like` VALUES (116, '6hj43ueim2bk187sqzcoy59v', '6v7be19pwman2fird04gqu53', '2019-01-06 19:58:07'); + +-- ---------------------------- +-- Table structure for pear_task_member +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_member`; +CREATE TABLE `pear_task_member` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `task_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '0' COMMENT '任务ID', + `is_executor` tinyint(1) NULL DEFAULT 0 COMMENT '执行者', + `member_code` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT '' COMMENT '成员id', + `join_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_owner` tinyint(1) NULL DEFAULT 0 COMMENT '是否创建人', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `id`(`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 263 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '任务-成员表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_task_member +-- ---------------------------- +INSERT INTO `pear_task_member` VALUES (1, 'mv4usefb06dxv8ez2spkl223', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-04-30 22:33:22', 0); +INSERT INTO `pear_task_member` VALUES (4, 'c3s1n5avuqgeoh2xb4yt809l', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:39:34', 0); +INSERT INTO `pear_task_member` VALUES (5, 'tx6loaugrd0s3e1mhk52iznp', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:47:08', 0); +INSERT INTO `pear_task_member` VALUES (6, 'n156qrj4l8720xhvioefmzys', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:47:26', 0); +INSERT INTO `pear_task_member` VALUES (7, 'b7upmiofztckvy6s38lxge90', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:48:17', 0); +INSERT INTO `pear_task_member` VALUES (8, '9av8miueqc7wbzo50ljg3p1r', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:49:16', 0); +INSERT INTO `pear_task_member` VALUES (9, '9av8miueqc7wbzo50ljg3p1r', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:49:16', 0); +INSERT INTO `pear_task_member` VALUES (10, '7agxbmcn4y1rzviw26s0du8p', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 14:53:46', 0); +INSERT INTO `pear_task_member` VALUES (11, 'kd2vz0jeyhwcl1ir9m8f64ap', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 14:54:56', 0); +INSERT INTO `pear_task_member` VALUES (12, 'kd2vz0jeyhwcl1ir9m8f64ap', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 14:54:56', 0); +INSERT INTO `pear_task_member` VALUES (13, 'ycs3hzrpfmjq4o19a8k2x7ln', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:01:39', 0); +INSERT INTO `pear_task_member` VALUES (14, 'ycs3hzrpfmjq4o19a8k2x7ln', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:01:39', 0); +INSERT INTO `pear_task_member` VALUES (15, 'yqugz409cvs8p165fx2miodn', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:02:18', 0); +INSERT INTO `pear_task_member` VALUES (16, 'yd5blem7xikvq01ujhwfzc4n', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:02:47', 0); +INSERT INTO `pear_task_member` VALUES (17, '9u0j2xvr37og8n1bpk6dwfsz', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:03:06', 0); +INSERT INTO `pear_task_member` VALUES (18, '9u0j2xvr37og8n1bpk6dwfsz', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:03:06', 0); +INSERT INTO `pear_task_member` VALUES (19, 'caq96fw7hnsv1pude2mibxz8', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:03:09', 0); +INSERT INTO `pear_task_member` VALUES (20, 'caq96fw7hnsv1pude2mibxz8', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:03:09', 0); +INSERT INTO `pear_task_member` VALUES (21, 'ubxoy07emt4lfij2cn961spd', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-25 15:03:11', 0); +INSERT INTO `pear_task_member` VALUES (22, 'ubxoy07emt4lfij2cn961spd', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:03:11', 0); +INSERT INTO `pear_task_member` VALUES (23, '3kpstlv16ixmaho78fuqc05e', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:07:41', 0); +INSERT INTO `pear_task_member` VALUES (24, 'uwdo4eytilf2bcx1qn5pz8vg', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:08:06', 0); +INSERT INTO `pear_task_member` VALUES (25, '0ib1k5v2pn4mdjsxa3eo6f8y', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:08:19', 0); +INSERT INTO `pear_task_member` VALUES (26, 'ndu3t1r7i09x6al2egsopzyf', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:16:55', 0); +INSERT INTO `pear_task_member` VALUES (27, '2wkhps0gviqcmfr5y8t1nu6d', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:19:26', 0); +INSERT INTO `pear_task_member` VALUES (28, 'sgkw8x2nte10o97i3dyumv4a', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:02', 0); +INSERT INTO `pear_task_member` VALUES (29, 'lremxqhjw265i3ku4p0bo8n1', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:40', 0); +INSERT INTO `pear_task_member` VALUES (30, 'dfcolxj2izhk08pq7stmu15v', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:42', 0); +INSERT INTO `pear_task_member` VALUES (31, 'j6xkdynh4c2sm1pblvztaweg', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:50', 0); +INSERT INTO `pear_task_member` VALUES (32, '7y9o8jrfxus3ca1i4bwvptgh', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:51', 0); +INSERT INTO `pear_task_member` VALUES (33, '6zkho2cegrl8fxq9wia1jmsp', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 15:20:52', 0); +INSERT INTO `pear_task_member` VALUES (35, '7qcosftd9bl83i62ugymkxaz', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:33:14', 0); +INSERT INTO `pear_task_member` VALUES (36, '98lqy4vaiprk60uzbojdmsgx', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:33:23', 0); +INSERT INTO `pear_task_member` VALUES (37, 'i7s50ny8j9p2km3hfau6le4r', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:03', 0); +INSERT INTO `pear_task_member` VALUES (38, 'kr5vojwa2hd370mxpgs984zt', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:09', 0); +INSERT INTO `pear_task_member` VALUES (39, 'edbn6rz89fmh7a3ilgvukw14', 0, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:11', 0); +INSERT INTO `pear_task_member` VALUES (40, 'fb3hsvx1e6tjad450y9cgr8o', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 17:47:15', 0); +INSERT INTO `pear_task_member` VALUES (41, 'f09otzl5e1r8qspgcvdy4ukx', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 20:09:17', 0); +INSERT INTO `pear_task_member` VALUES (42, 'r3efvs51qn9dy0c6mik2u7xw', 1, '6v7be19pwman2fird04gqu53', '2018-12-25 21:48:48', 0); +INSERT INTO `pear_task_member` VALUES (43, 'zabrco15knhvj4xwm69yd7et', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 09:08:15', 0); +INSERT INTO `pear_task_member` VALUES (44, '0tjma1un2gz8rf4ywo7c6de9', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:17', 0); +INSERT INTO `pear_task_member` VALUES (45, 'xkqg60sld15fcphwt4ya3rb8', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 11:28:22', 0); +INSERT INTO `pear_task_member` VALUES (46, 'bodic0rp491g837vah5tsxqn', 0, '6v7be19pwman2fird04gqu53', '2018-12-26 11:30:32', 0); +INSERT INTO `pear_task_member` VALUES (47, '3urs09e57btygqhjdfx2pwmn', 1, '6v7be19pwman2fird04gqu53', '2018-12-26 16:08:36', 0); +INSERT INTO `pear_task_member` VALUES (53, 'aut9wrz1pn0elf5s47ivx26o', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-27 14:45:06', 1); +INSERT INTO `pear_task_member` VALUES (65, 'sgkw8x2nte10o97i3dyumv4a', 1, 'kqdcn2w40p58r31zyo6efjib', '2018-12-27 15:19:22', 0); +INSERT INTO `pear_task_member` VALUES (66, 'dfcolxj2izhk08pq7stmu15v', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-27 15:20:42', 0); +INSERT INTO `pear_task_member` VALUES (67, 'edbn6rz89fmh7a3ilgvukw14', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-27 15:21:15', 0); +INSERT INTO `pear_task_member` VALUES (68, 'l09jk1p7z4x3ebm2rvwsuthn', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:00:53', 1); +INSERT INTO `pear_task_member` VALUES (69, '4mtnhwbe0gjdkaur2ic7xsv6', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:02:28', 1); +INSERT INTO `pear_task_member` VALUES (70, 'hj5s73zk6amd9wfvbxoygpic', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:12:06', 1); +INSERT INTO `pear_task_member` VALUES (71, 'l027b1dyrv93zu4ewmtoa6q5', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:14:09', 1); +INSERT INTO `pear_task_member` VALUES (72, 'xu7cpf2h3t6o0rilam8k9sgj', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:19:13', 1); +INSERT INTO `pear_task_member` VALUES (73, '0zvn3ug6fiqhdpkljos79xaw', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:42:23', 1); +INSERT INTO `pear_task_member` VALUES (74, 'we7iyhrudos9lt50cn8fjzp2', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 13:45:08', 0); +INSERT INTO `pear_task_member` VALUES (75, 'we7iyhrudos9lt50cn8fjzp2', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:45:09', 1); +INSERT INTO `pear_task_member` VALUES (76, '6kqh4b1mce05rzvuljsg3ow8', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:45:19', 1); +INSERT INTO `pear_task_member` VALUES (77, 'g83vs5t47dfnprchqzel1a29', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 13:45:27', 0); +INSERT INTO `pear_task_member` VALUES (78, 'g83vs5t47dfnprchqzel1a29', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 13:45:27', 1); +INSERT INTO `pear_task_member` VALUES (79, 'mevzwp1d086roxsb92n4yja7', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 14:36:29', 1); +INSERT INTO `pear_task_member` VALUES (80, '5qd1lfth0gji4aeb3oczp9u8', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 14:36:32', 1); +INSERT INTO `pear_task_member` VALUES (81, 'bodic0rp491g837vah5tsxqn', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:02:24', 0); +INSERT INTO `pear_task_member` VALUES (82, 'ua7rphit5fxj04l6qc8nydze', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:08:19', 1); +INSERT INTO `pear_task_member` VALUES (83, 'ua7rphit5fxj04l6qc8nydze', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:09:53', 0); +INSERT INTO `pear_task_member` VALUES (84, 'yis4dg3txpoah6jnr290v57w', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:56:32', 0); +INSERT INTO `pear_task_member` VALUES (85, 'yis4dg3txpoah6jnr290v57w', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:56:32', 1); +INSERT INTO `pear_task_member` VALUES (86, 'bzn09o6a3j1qe4sp7il8tvxy', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:56:35', 0); +INSERT INTO `pear_task_member` VALUES (87, 'bzn09o6a3j1qe4sp7il8tvxy', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:56:35', 1); +INSERT INTO `pear_task_member` VALUES (88, '3gkj1lmzxpcf5e628thnwsb7', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 15:56:40', 0); +INSERT INTO `pear_task_member` VALUES (89, '3gkj1lmzxpcf5e628thnwsb7', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 15:56:40', 1); +INSERT INTO `pear_task_member` VALUES (90, 'fr091dxea76hvzgp42ywctmj', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:18:57', 0); +INSERT INTO `pear_task_member` VALUES (91, 'fr091dxea76hvzgp42ywctmj', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:18:58', 1); +INSERT INTO `pear_task_member` VALUES (92, 'n6txie0u7mz8cghyw1kjaof5', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:18:59', 0); +INSERT INTO `pear_task_member` VALUES (93, 'n6txie0u7mz8cghyw1kjaof5', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:18:59', 1); +INSERT INTO `pear_task_member` VALUES (94, '8uhmp6ekvwl5zny7gr3bo9a1', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:19:00', 0); +INSERT INTO `pear_task_member` VALUES (95, '8uhmp6ekvwl5zny7gr3bo9a1', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:00', 1); +INSERT INTO `pear_task_member` VALUES (96, 'ibc0m5jly7k4wngfper2dstx', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-28 23:19:01', 0); +INSERT INTO `pear_task_member` VALUES (97, 'ibc0m5jly7k4wngfper2dstx', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:01', 1); +INSERT INTO `pear_task_member` VALUES (98, 'lcxa0u8srgkq46fy7pw2b3o5', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-28 23:19:06', 0); +INSERT INTO `pear_task_member` VALUES (99, 'lcxa0u8srgkq46fy7pw2b3o5', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:06', 1); +INSERT INTO `pear_task_member` VALUES (100, 'sora9u5ti4zlxhy3men12vb8', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:18', 1); +INSERT INTO `pear_task_member` VALUES (101, 'rat4seg68kpi0yqv793fux1l', 1, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:20', 1); +INSERT INTO `pear_task_member` VALUES (102, 'aryd8zjbh3f20oln4gekv57x', 0, '6v7be19pwman2fird04gqu53', '2018-12-28 23:19:30', 1); +INSERT INTO `pear_task_member` VALUES (106, 'oz2xwp8v0niahdc7lekjtsrg', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 11:02:39', 1); +INSERT INTO `pear_task_member` VALUES (107, 'v2kr731dihezctslmx9agb5w', 0, 'kqdcn2w40p58r31zyo6efjib', '2018-12-29 11:02:49', 0); +INSERT INTO `pear_task_member` VALUES (108, 'v2kr731dihezctslmx9agb5w', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 11:02:49', 1); +INSERT INTO `pear_task_member` VALUES (109, '1vqt5zg4shbkum2dc8jxow7f', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:10:16', 0); +INSERT INTO `pear_task_member` VALUES (110, '1vqt5zg4shbkum2dc8jxow7f', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:10:16', 1); +INSERT INTO `pear_task_member` VALUES (111, '2hg0yfa9jpxz6q58ckrdol1u', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:11:40', 0); +INSERT INTO `pear_task_member` VALUES (112, '2hg0yfa9jpxz6q58ckrdol1u', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:11:40', 1); +INSERT INTO `pear_task_member` VALUES (113, 'bi5ajpmxfsk9rwdg4l32yv1n', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:11:57', 0); +INSERT INTO `pear_task_member` VALUES (114, 'bi5ajpmxfsk9rwdg4l32yv1n', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:11:57', 1); +INSERT INTO `pear_task_member` VALUES (115, 'dmbtgy3phi2sz7j89q10xcoa', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-29 12:12:32', 0); +INSERT INTO `pear_task_member` VALUES (116, 'dmbtgy3phi2sz7j89q10xcoa', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:32', 1); +INSERT INTO `pear_task_member` VALUES (117, 'ywiahqcb50rkem376js9nflv', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:41', 1); +INSERT INTO `pear_task_member` VALUES (118, 'xebf08p2uc9sj6mkr5714lat', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:42', 1); +INSERT INTO `pear_task_member` VALUES (119, 'p5axvq94lr7enmhb83o2zfks', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:12:44', 1); +INSERT INTO `pear_task_member` VALUES (120, 'lert5uyi790q2jfpbmzgak1o', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:14:21', 1); +INSERT INTO `pear_task_member` VALUES (121, '25rzhoykix6pajsw9cgm1408', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:14:30', 1); +INSERT INTO `pear_task_member` VALUES (122, 'cmrdn8soz4g26fy0qa3ib7te', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:15:33', 1); +INSERT INTO `pear_task_member` VALUES (123, 'qfakryig3ztpv5uw6e2obx08', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:16:19', 1); +INSERT INTO `pear_task_member` VALUES (124, 'e16tvkg4uoixnz9hjaf5l3wr', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:17', 1); +INSERT INTO `pear_task_member` VALUES (125, 'f35oyrln286s7p4u9vmeghdw', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:18', 1); +INSERT INTO `pear_task_member` VALUES (126, 'yqwe2aiupvg5zj1so4krm86l', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:19', 1); +INSERT INTO `pear_task_member` VALUES (127, 'yhs4xb7g3vu9pnwfq5l1zioa', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:22', 1); +INSERT INTO `pear_task_member` VALUES (128, 'jo3vbswk8ze57filmcptun2g', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:52', 1); +INSERT INTO `pear_task_member` VALUES (129, '35i8ptd1fs9hrbk0glv47ycj', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:53', 1); +INSERT INTO `pear_task_member` VALUES (130, 'pn3dmjavhrc1bewgi4yz25x9', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:54', 1); +INSERT INTO `pear_task_member` VALUES (131, 'p5r8a4gwfxmn9sqit2jvkl71', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:22:55', 1); +INSERT INTO `pear_task_member` VALUES (132, 'aenxk82rgwm5qcsuo0b7hi4f', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:23:00', 1); +INSERT INTO `pear_task_member` VALUES (133, 'vf8xtpclba3od21kmih5us9q', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:24:25', 1); +INSERT INTO `pear_task_member` VALUES (134, '2ohuv1jm3abrs8ldni5p9z06', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:24:26', 1); +INSERT INTO `pear_task_member` VALUES (135, 'wamzl34n6jfrhiyoe09v2ktb', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:39', 1); +INSERT INTO `pear_task_member` VALUES (136, 'rgshi61c4xol5ue9f2n8m3bd', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:40', 1); +INSERT INTO `pear_task_member` VALUES (137, 'kny3xf7o41rli9s0pwjmhdqc', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:41', 1); +INSERT INTO `pear_task_member` VALUES (138, 'y2zos3hg9058alwet46xmukp', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:42', 1); +INSERT INTO `pear_task_member` VALUES (139, 'wqu7verc5i4p19h0y32x6tmo', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:43', 1); +INSERT INTO `pear_task_member` VALUES (140, '0h8mt7sw1ki5fuxv2y6d3jag', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:44', 1); +INSERT INTO `pear_task_member` VALUES (141, '1hcrmvgfjtu5qnadl869bkxo', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:46', 1); +INSERT INTO `pear_task_member` VALUES (142, 'puj84l5av3f0e7k9oyw2zqcr', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:54', 1); +INSERT INTO `pear_task_member` VALUES (143, 'jftz4y5is1hwrlmac07nd8q6', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 12:29:56', 1); +INSERT INTO `pear_task_member` VALUES (144, '4fua38vpqgk706csx2lb9etj', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 13:05:03', 1); +INSERT INTO `pear_task_member` VALUES (146, 's5bxrym2p8qtjch1i6gnkvaw', 1, '6v7be19pwman2fird04gqu53', '2018-12-29 16:08:33', 1); +INSERT INTO `pear_task_member` VALUES (147, 'aut9wrz1pn0elf5s47ivx26o', 0, '6v7be19pwman2fird04gqu53', '2018-12-29 21:47:17', 0); +INSERT INTO `pear_task_member` VALUES (148, 'w80m92aopfbcru6s5qe7z3ti', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 10:24:40', 1); +INSERT INTO `pear_task_member` VALUES (149, 'wrjgk84t2beam0yvxs61qinu', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 10:24:42', 1); +INSERT INTO `pear_task_member` VALUES (150, 'n9pe164krv8zghofilw750jq', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 15:38:03', 1); +INSERT INTO `pear_task_member` VALUES (151, 'oq7d3wbklenf12pgvuxhimr8', 1, '6v7be19pwman2fird04gqu53', '2018-12-30 15:39:15', 1); +INSERT INTO `pear_task_member` VALUES (153, 'mv4usefb06dxv8ez2spkl223', 0, '6v7be19pwman2fird04gqu53', '2018-12-31 10:49:34', 0); +INSERT INTO `pear_task_member` VALUES (154, 'mv4usefb06dxv8ez2spkl223', 0, 'y680trgedcavbhnz24u7i5m3', '2018-12-31 10:49:34', 0); +INSERT INTO `pear_task_member` VALUES (155, 'qscug70y98zpk6edbnf3livr', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:43', 1); +INSERT INTO `pear_task_member` VALUES (156, 'rzpu5cxl63fvb2y8gwdnsjqk', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:44', 1); +INSERT INTO `pear_task_member` VALUES (157, 'ozi8awms1lpcbde4fuq5ktgj', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:45', 1); +INSERT INTO `pear_task_member` VALUES (158, 'xejt6431q8ly97bkid5z2pun', 0, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:46', 1); +INSERT INTO `pear_task_member` VALUES (159, 'zkqb6if5ogdts27lx13r4yju', 1, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:47', 1); +INSERT INTO `pear_task_member` VALUES (160, '9wsohy8jgapl6x2iutbm7k34', 0, '6v7be19pwman2fird04gqu53', '2018-12-31 15:04:49', 1); +INSERT INTO `pear_task_member` VALUES (161, 'xejt6431q8ly97bkid5z2pun', 1, 'y680trgedcavbhnz24u7i5m3', '2018-12-31 15:23:08', 0); +INSERT INTO `pear_task_member` VALUES (162, 'q9y6ksvtifwpuhna0e32jgm1', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:04', 1); +INSERT INTO `pear_task_member` VALUES (163, 'wyklgmhpt5qr47x3zsf9nibj', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 10:46:14', 1); +INSERT INTO `pear_task_member` VALUES (164, 'wyklgmhpt5qr47x3zsf9nibj', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 10:51:44', 0); +INSERT INTO `pear_task_member` VALUES (165, 'm6cloqrbh7tf0wg1jsvp9nay', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 11:00:15', 0); +INSERT INTO `pear_task_member` VALUES (166, 'm6cloqrbh7tf0wg1jsvp9nay', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 11:00:15', 1); +INSERT INTO `pear_task_member` VALUES (167, 'p1aujdigrlxky76h8cs3z4w0', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:30', 1); +INSERT INTO `pear_task_member` VALUES (168, '2bn918l6ejyzousa73dkpgci', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:37', 1); +INSERT INTO `pear_task_member` VALUES (169, '3qz5hfsin69xt8cgbd70lkew', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:45', 1); +INSERT INTO `pear_task_member` VALUES (170, 'xkic58d20srnu9jm7ohqw14f', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:25:53', 1); +INSERT INTO `pear_task_member` VALUES (171, '6hj43ueim2bk187sqzcoy59v', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:01', 1); +INSERT INTO `pear_task_member` VALUES (172, 'twb8f52jasn9vry6iko0dqg4', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:09', 1); +INSERT INTO `pear_task_member` VALUES (173, 'gjmotpbrwva079ukde4izn38', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:16', 1); +INSERT INTO `pear_task_member` VALUES (174, 'uwq87z2f0hnvrl6o9gtcb3iy', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:26:21', 1); +INSERT INTO `pear_task_member` VALUES (175, 'qug5e4alndm7930ipxwyvc2h', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:04', 1); +INSERT INTO `pear_task_member` VALUES (176, 'yctbsv81x6dmahkf7ei5o4r9', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:17', 1); +INSERT INTO `pear_task_member` VALUES (177, 'm7u8fdp41cwrtkjxyzq2ion3', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:30', 1); +INSERT INTO `pear_task_member` VALUES (178, 'jo0i8fq2579kbdgsmcw1nev4', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:36', 1); +INSERT INTO `pear_task_member` VALUES (179, 'owrs04m3e2klj8uqac6tiy17', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:27:54', 1); +INSERT INTO `pear_task_member` VALUES (180, 'g15scwqm9zxroy7p8bvjt632', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:21', 1); +INSERT INTO `pear_task_member` VALUES (181, '0a84xkg12enqjml7rz6dbifw', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:25', 1); +INSERT INTO `pear_task_member` VALUES (182, 'fax4gez2jlk15tvsu3dc6p98', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:29', 1); +INSERT INTO `pear_task_member` VALUES (183, 'zv4hx1ugpn98be5skc3wym72', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:33', 1); +INSERT INTO `pear_task_member` VALUES (184, 'jiy25eobh1cnp7ruvg9d0m6s', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:39', 1); +INSERT INTO `pear_task_member` VALUES (185, '4pv9brqnm0cigwu5f3zeyxdk', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:43', 1); +INSERT INTO `pear_task_member` VALUES (186, 'td1qznl9ms65gbcfej0k4vup', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:50', 1); +INSERT INTO `pear_task_member` VALUES (187, 'fkrsvpzmj8xyo045hiugqt92', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:28:56', 1); +INSERT INTO `pear_task_member` VALUES (188, '0b6wlc3754fr8gdvupx9aoys', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:02', 1); +INSERT INTO `pear_task_member` VALUES (189, 'bl1t7xjwpi9m2aocnsz83fk6', 0, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:18', 1); +INSERT INTO `pear_task_member` VALUES (190, 'hxntygarp3094c7w1856iujm', 1, '6v7be19pwman2fird04gqu53', '2019-01-03 22:29:24', 1); +INSERT INTO `pear_task_member` VALUES (191, 'bl1t7xjwpi9m2aocnsz83fk6', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:29:42', 0); +INSERT INTO `pear_task_member` VALUES (192, '0a84xkg12enqjml7rz6dbifw', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:30:00', 0); +INSERT INTO `pear_task_member` VALUES (193, 'td1qznl9ms65gbcfej0k4vup', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:30:15', 0); +INSERT INTO `pear_task_member` VALUES (194, 'uwq87z2f0hnvrl6o9gtcb3iy', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:30:27', 0); +INSERT INTO `pear_task_member` VALUES (195, '3qz5hfsin69xt8cgbd70lkew', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:30:31', 0); +INSERT INTO `pear_task_member` VALUES (196, 'xkic58d20srnu9jm7ohqw14f', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-03 22:30:38', 0); +INSERT INTO `pear_task_member` VALUES (197, 'owrs04m3e2klj8uqac6tiy17', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-03 22:30:46', 0); +INSERT INTO `pear_task_member` VALUES (198, '6hj43ueim2bk187sqzcoy59v', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 08:57:04', 0); +INSERT INTO `pear_task_member` VALUES (199, 'gk8ipqm5406br7cwd9l1zefs', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:09:29', 1); +INSERT INTO `pear_task_member` VALUES (200, 'm7u8fdp41cwrtkjxyzq2ion3', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:17:51', 0); +INSERT INTO `pear_task_member` VALUES (201, 'o61b3s24exmcy8njkparwthd', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 09:18:18', 0); +INSERT INTO `pear_task_member` VALUES (202, 'o61b3s24exmcy8njkparwthd', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:18', 1); +INSERT INTO `pear_task_member` VALUES (203, 'orycwlhf7n2qx1pta038dzjk', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 09:18:24', 0); +INSERT INTO `pear_task_member` VALUES (204, 'orycwlhf7n2qx1pta038dzjk', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:24', 1); +INSERT INTO `pear_task_member` VALUES (205, 'yctbsv81x6dmahkf7ei5o4r9', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 09:18:50', 0); +INSERT INTO `pear_task_member` VALUES (206, 'p1aujdigrlxky76h8cs3z4w0', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 09:19:58', 0); +INSERT INTO `pear_task_member` VALUES (207, 'up6hn9bd34c8mglwaj1ytefz', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:13', 1); +INSERT INTO `pear_task_member` VALUES (208, 'krj4p7ix2cf605vyltmudq1e', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:19', 1); +INSERT INTO `pear_task_member` VALUES (209, '1g3vc8tkyla20fp5rdhxe7mo', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:17:46', 0); +INSERT INTO `pear_task_member` VALUES (210, '1g3vc8tkyla20fp5rdhxe7mo', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:17:46', 1); +INSERT INTO `pear_task_member` VALUES (211, 'nqrleu2c90zsdaj1yph4m8bt', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:18:25', 0); +INSERT INTO `pear_task_member` VALUES (212, 'nqrleu2c90zsdaj1yph4m8bt', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:25', 1); +INSERT INTO `pear_task_member` VALUES (213, 'mix3cg2eh1u60fknd7yz9v5t', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:18:37', 0); +INSERT INTO `pear_task_member` VALUES (214, 'mix3cg2eh1u60fknd7yz9v5t', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:37', 1); +INSERT INTO `pear_task_member` VALUES (215, 'dckxz1vpujtafshgr20mwo7e', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:18:45', 0); +INSERT INTO `pear_task_member` VALUES (216, 'dckxz1vpujtafshgr20mwo7e', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:45', 1); +INSERT INTO `pear_task_member` VALUES (217, 'fd1avskez2q43w80xhb7ypc9', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:18:53', 0); +INSERT INTO `pear_task_member` VALUES (218, 'fd1avskez2q43w80xhb7ypc9', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:18:53', 1); +INSERT INTO `pear_task_member` VALUES (219, 'as2y4r6mwxuhgvncop3f8z90', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:01', 1); +INSERT INTO `pear_task_member` VALUES (220, '8zj3vpx0b7qud24ylfgces1m', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:19:08', 0); +INSERT INTO `pear_task_member` VALUES (221, '8zj3vpx0b7qud24ylfgces1m', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:08', 1); +INSERT INTO `pear_task_member` VALUES (222, 'hcrdvbuzwgojst2f0p134qxi', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:19:18', 0); +INSERT INTO `pear_task_member` VALUES (223, 'hcrdvbuzwgojst2f0p134qxi', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:18', 1); +INSERT INTO `pear_task_member` VALUES (224, 'lmognshqz21dbewcu9a3rx87', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:19:37', 0); +INSERT INTO `pear_task_member` VALUES (225, 'lmognshqz21dbewcu9a3rx87', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:37', 1); +INSERT INTO `pear_task_member` VALUES (226, 'n6ulc7ebxpqahi50dy9k1sgf', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:51', 1); +INSERT INTO `pear_task_member` VALUES (227, 'rqjng1kfcp4wyiamt6o23zbu', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:19:57', 0); +INSERT INTO `pear_task_member` VALUES (228, 'rqjng1kfcp4wyiamt6o23zbu', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:19:57', 1); +INSERT INTO `pear_task_member` VALUES (229, 'qsz65fvgi8hyx3e7bn14o9wm', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:20:05', 0); +INSERT INTO `pear_task_member` VALUES (230, 'qsz65fvgi8hyx3e7bn14o9wm', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:05', 1); +INSERT INTO `pear_task_member` VALUES (231, 'byiuxhn0v6sod4zap1t2fclr', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:20:12', 0); +INSERT INTO `pear_task_member` VALUES (232, 'byiuxhn0v6sod4zap1t2fclr', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:12', 1); +INSERT INTO `pear_task_member` VALUES (233, 'jxd3rpmay6qonsk1i8wg5e9u', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:20:27', 0); +INSERT INTO `pear_task_member` VALUES (234, 'jxd3rpmay6qonsk1i8wg5e9u', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:27', 1); +INSERT INTO `pear_task_member` VALUES (235, 'vmzeciodgbfp7ysu38tq10kj', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:33', 1); +INSERT INTO `pear_task_member` VALUES (236, '6cagd725tifonvw0qphe9zsb', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:45', 1); +INSERT INTO `pear_task_member` VALUES (237, 'xu3jgyow2s9f1km0rctqin4v', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:20:54', 0); +INSERT INTO `pear_task_member` VALUES (238, 'xu3jgyow2s9f1km0rctqin4v', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:20:54', 1); +INSERT INTO `pear_task_member` VALUES (239, 'k3g07m1qyctvbp95siohju6f', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:21:12', 0); +INSERT INTO `pear_task_member` VALUES (240, 'k3g07m1qyctvbp95siohju6f', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:12', 1); +INSERT INTO `pear_task_member` VALUES (241, 'oh5wpj9kd8e6ltusxq271ma3', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:21:18', 0); +INSERT INTO `pear_task_member` VALUES (242, 'oh5wpj9kd8e6ltusxq271ma3', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:18', 1); +INSERT INTO `pear_task_member` VALUES (243, 'akdwslbtp3z82xecui0y4ovq', 0, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:21:25', 0); +INSERT INTO `pear_task_member` VALUES (244, 'akdwslbtp3z82xecui0y4ovq', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:25', 1); +INSERT INTO `pear_task_member` VALUES (245, 'hayfr6vl398nq5exgszobu2j', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:31', 1); +INSERT INTO `pear_task_member` VALUES (246, 'mf80iu15kepavbg2r9ldcjsh', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:21:42', 0); +INSERT INTO `pear_task_member` VALUES (247, 'mf80iu15kepavbg2r9ldcjsh', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:21:42', 1); +INSERT INTO `pear_task_member` VALUES (248, 'nzy71f5i6g0skwau4lrj3d8b', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:22:08', 0); +INSERT INTO `pear_task_member` VALUES (249, 'nzy71f5i6g0skwau4lrj3d8b', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:08', 1); +INSERT INTO `pear_task_member` VALUES (250, '4cug3e5rodalq9x81ywht0zn', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:22:20', 0); +INSERT INTO `pear_task_member` VALUES (251, '4cug3e5rodalq9x81ywht0zn', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:20', 1); +INSERT INTO `pear_task_member` VALUES (252, '92fow0le47htb6xkv5ynzuri', 0, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:22:24', 0); +INSERT INTO `pear_task_member` VALUES (253, '92fow0le47htb6xkv5ynzuri', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:24', 1); +INSERT INTO `pear_task_member` VALUES (254, '6ky18i9cg0eqvfzn2th3ux5l', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:34', 1); +INSERT INTO `pear_task_member` VALUES (255, 'zj6skt9orn748gh5mvb2ueif', 0, '6v7be19pwman2fird04gqu53', '2019-01-04 21:22:40', 1); +INSERT INTO `pear_task_member` VALUES (256, 'a75dcqx2sjivokmg49yh380l', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:13', 1); +INSERT INTO `pear_task_member` VALUES (257, '7ns924ofulpjxkgq06y3bm5r', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:30:19', 1); +INSERT INTO `pear_task_member` VALUES (258, 'up6hn9bd34c8mglwaj1ytefz', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:02', 0); +INSERT INTO `pear_task_member` VALUES (259, 'as2y4r6mwxuhgvncop3f8z90', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:13', 0); +INSERT INTO `pear_task_member` VALUES (260, 'vmzeciodgbfp7ysu38tq10kj', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:25', 0); +INSERT INTO `pear_task_member` VALUES (261, '6cagd725tifonvw0qphe9zsb', 1, 'kqdcn2w40p58r31zyo6efjib', '2019-01-04 21:38:30', 0); +INSERT INTO `pear_task_member` VALUES (262, 'zj6skt9orn748gh5mvb2ueif', 1, 'y680trgedcavbhnz24u7i5m3', '2019-01-04 21:38:45', 0); + +-- ---------------------------- +-- Table structure for pear_task_stages +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_stages`; +CREATE TABLE `pear_task_stages` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', + `project_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '' COMMENT '项目id', + `sort` int(11) NULL DEFAULT 0 COMMENT '排序', + `description` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL COMMENT '备注', + `create_time` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '创建时间', + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + `deleted` tinyint(1) NULL DEFAULT 0 COMMENT '删除标记', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 72 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务列表表' ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_task_stages +-- ---------------------------- +INSERT INTO `pear_task_stages` VALUES (33, '修复', 'mo4uqwfb06dxv8ez2spkl3rg', 33, NULL, '2018-12-25 07:20:36', '7z8tgb6xevy2aj9nui5fk0w1', 0); +INSERT INTO `pear_task_stages` VALUES (34, '重构', 'mo4uqwfb06dxv8ez2spkl3rg', 34, NULL, '2018-12-25 07:20:36', 'g0yw3r54qahbk7lets6fv2on', 0); +INSERT INTO `pear_task_stages` VALUES (35, '升级', 'mo4uqwfb06dxv8ez2spkl3rg', 35, NULL, '2018-12-25 07:20:36', 'psemnf3ugo89vc5r2hkxid1t', 0); +INSERT INTO `pear_task_stages` VALUES (36, '优化', 'mo4uqwfb06dxv8ez2spkl3rg', 37, NULL, '2018-12-25 07:20:36', 'p56enm7zck4id2rb0tx9lguh', 0); +INSERT INTO `pear_task_stages` VALUES (37, '新增', 'mo4uqwfb06dxv8ez2spkl3rg', 36, NULL, '2018-12-25 07:20:36', 'jvyswuxz34qk2cpt9o7ldb60', 0); +INSERT INTO `pear_task_stages` VALUES (38, '协议签订', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:35:47', 'oijw2ds86lf7zp1chvq03r5e', 0); +INSERT INTO `pear_task_stages` VALUES (39, '图纸设计', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:35:53', 'ipzyscgfo5l1qvah2xm4638t', 0); +INSERT INTO `pear_task_stages` VALUES (40, '评审及打样', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:21', '3uz8afjkxnogwivd9s0lqp7y', 0); +INSERT INTO `pear_task_stages` VALUES (41, '构件采购', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:34', 'f6dp4ur1zc2omswtyhnbixe3', 0); +INSERT INTO `pear_task_stages` VALUES (42, '制造安装', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:40', 'imltk4y2se1rbzafohw8x5p6', 0); +INSERT INTO `pear_task_stages` VALUES (43, '内部检验', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:45', 'miwt9bd6saxge2pvn31h0zky', 0); +INSERT INTO `pear_task_stages` VALUES (44, '测试', 'ibag9hw3o1tusd5qlpxrk782', 0, NULL, '2018-12-26 08:36:52', 'ao18rjcszwh2bm6inypxgkv0', 0); +INSERT INTO `pear_task_stages` VALUES (48, '产品计划', 'p94ckbwv5lyxt2rhzeam3s86', 0, NULL, '2019-01-02 11:17:27', 'xt4ne81fu9jgayw2szokr5q3', 0); +INSERT INTO `pear_task_stages` VALUES (49, '即将发布', 'p94ckbwv5lyxt2rhzeam3s86', 1, NULL, '2019-01-02 11:17:27', 'c9ro6jxpl25wbmuvfy840kqe', 0); +INSERT INTO `pear_task_stages` VALUES (50, '测试', 'p94ckbwv5lyxt2rhzeam3s86', 2, NULL, '2019-01-02 11:17:27', 'inxmfhz8kvqes1w39a2oc5j6', 0); +INSERT INTO `pear_task_stages` VALUES (51, '准备发布', 'p94ckbwv5lyxt2rhzeam3s86', 3, NULL, '2019-01-02 11:17:27', '2zm1n3g8ikaseow5cfp9h7dx', 0); +INSERT INTO `pear_task_stages` VALUES (52, '发布成功', 'p94ckbwv5lyxt2rhzeam3s86', 4, NULL, '2019-01-02 11:17:27', '3jdozmqf4tcakyixgn750ule', 0); +INSERT INTO `pear_task_stages` VALUES (53, '产品计划', '8ulzfth64cd0k1x5peivowm2', 0, NULL, '2019-01-03 09:15:11', 'pfi2ltmjhxuda90ncsgb5vwo', 0); +INSERT INTO `pear_task_stages` VALUES (54, '即将发布', '8ulzfth64cd0k1x5peivowm2', 2, NULL, '2019-01-03 09:15:11', 'ht0gfnevaq7kp3ldx16i82yj', 0); +INSERT INTO `pear_task_stages` VALUES (55, '测试', '8ulzfth64cd0k1x5peivowm2', 1, NULL, '2019-01-03 09:15:11', 'dot8li21nx437ypksjav59wf', 0); +INSERT INTO `pear_task_stages` VALUES (56, '准备发布', '8ulzfth64cd0k1x5peivowm2', 3, NULL, '2019-01-03 09:15:11', 'p0re71zhm48yxq63lfnjwkso', 0); +INSERT INTO `pear_task_stages` VALUES (57, '发布成功', '8ulzfth64cd0k1x5peivowm2', 4, NULL, '2019-01-03 09:15:11', 'k436eltf5zygbpnhrdqc8mo2', 0); +INSERT INTO `pear_task_stages` VALUES (61, 'ADD', 'elqa703jyvfhpt1dsxkzi8on', 61, NULL, '2019-01-04 21:15:46', '2sf7h3p01l5qgdeumrzny4bi', 0); +INSERT INTO `pear_task_stages` VALUES (62, 'Fix', 'elqa703jyvfhpt1dsxkzi8on', 62, NULL, '2019-01-04 21:15:51', 'njd4er1ohakl6bz258qcfgsv', 0); +INSERT INTO `pear_task_stages` VALUES (63, 'Change', 'elqa703jyvfhpt1dsxkzi8on', 63, NULL, '2019-01-04 21:16:07', 'oxcj9krmqeu08wbga2ftz7ls', 0); +INSERT INTO `pear_task_stages` VALUES (64, 'Update', 'elqa703jyvfhpt1dsxkzi8on', 64, NULL, '2019-01-04 21:16:29', 'sft603lxe5phk89ou1cgmiby', 0); +INSERT INTO `pear_task_stages` VALUES (65, 'Removed', 'elqa703jyvfhpt1dsxkzi8on', 65, NULL, '2019-01-04 21:16:49', '0jmqucy41h3rt9ag27wils6b', 0); +INSERT INTO `pear_task_stages` VALUES (66, '产品计划', 'gbim9jpevkh7qr6ufa1t3wl4', 0, NULL, '2019-01-05 21:57:31', 'j3f52swoct7earzhd6gxk41m', 0); +INSERT INTO `pear_task_stages` VALUES (67, '即将发布', 'gbim9jpevkh7qr6ufa1t3wl4', 1, NULL, '2019-01-05 21:57:31', '5fkwydvzopqrmxj0174nl93u', 0); +INSERT INTO `pear_task_stages` VALUES (68, '测试', 'gbim9jpevkh7qr6ufa1t3wl4', 2, NULL, '2019-01-05 21:57:31', '97gxmwyidlae4r2u1hqbcpnz', 0); +INSERT INTO `pear_task_stages` VALUES (69, '准备发布', 'gbim9jpevkh7qr6ufa1t3wl4', 3, NULL, '2019-01-05 21:57:31', '9f4vdsw7gzpo2hm1qbt0xyn6', 0); +INSERT INTO `pear_task_stages` VALUES (70, '发布成功', 'gbim9jpevkh7qr6ufa1t3wl4', 4, NULL, '2019-01-05 21:57:31', 'pm8129iltue7jnyvgb4d30xw', 0); + +-- ---------------------------- +-- Table structure for pear_task_stages_template +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_stages_template`; +CREATE TABLE `pear_task_stages_template` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型名称', + `project_template_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0' COMMENT '项目id', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `sort` int(11) NULL DEFAULT 0, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '编号', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 84 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '任务列表模板表' ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of pear_task_stages_template +-- ---------------------------- +INSERT INTO `pear_task_stages_template` VALUES (61, '待处理', 'un6125mxt4dcizhjqwvgyb3a', '2018-12-24 16:57:49', 1, 'ts0cdj5wzxnlhfymrvuk1ei9'); +INSERT INTO `pear_task_stages_template` VALUES (62, '进行中', 'un6125mxt4dcizhjqwvgyb3a', '2018-12-24 16:57:49', 0, '9vbjag5rl0ikxqyc146p7swf'); +INSERT INTO `pear_task_stages_template` VALUES (63, '已完成', 'un6125mxt4dcizhjqwvgyb3a', '2018-12-24 16:57:49', 0, 'l3o95r4wyk18bh2aq7xcz0ve'); +INSERT INTO `pear_task_stages_template` VALUES (65, '协议签订', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:33', 0, '4510enfyvjtzho3cw28xsagi'); +INSERT INTO `pear_task_stages_template` VALUES (66, '图纸设计', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:38', 0, '3esu4p172alok89wjmrvqihz'); +INSERT INTO `pear_task_stages_template` VALUES (67, '评审及打样', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:43', 0, 'e6jp81o7drfkzluxbhmiaqtv'); +INSERT INTO `pear_task_stages_template` VALUES (68, '构件采购', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:52', 0, 'tpy76njoair0clhz9xmeg482'); +INSERT INTO `pear_task_stages_template` VALUES (69, '制造安装', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:00:58', 0, 've97pldtbnjrqco1hyfx82sa'); +INSERT INTO `pear_task_stages_template` VALUES (70, '内部检验', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:01:04', 0, '4phrcltwygziu2s13jxbaqv8'); +INSERT INTO `pear_task_stages_template` VALUES (71, '验收', 'd85f1bvwpml2nhxe91zu7tyi', '2018-12-24 22:01:09', 0, 'qxi9n42p0w57jtrmyhz8gl3c'); +INSERT INTO `pear_task_stages_template` VALUES (72, '需求收集', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:01:30', 0, '48h13usk7en6ljyxbqgiw02z'); +INSERT INTO `pear_task_stages_template` VALUES (73, '评估确认', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:17', 0, '70z1fpxytvchbadkgsieowuj'); +INSERT INTO `pear_task_stages_template` VALUES (74, '需求暂缓', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:22', 0, 'bkyunf9jr2c37m4oi81sxzqp'); +INSERT INTO `pear_task_stages_template` VALUES (75, '研发中', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:27', 0, 'zu0vrhpoi835klgxqndmf6w9'); +INSERT INTO `pear_task_stages_template` VALUES (76, '内测中', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:32', 0, 'j4d5l7s6rgvk9o32ayt1uefc'); +INSERT INTO `pear_task_stages_template` VALUES (77, '通知用户', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:40', 0, 'cjk6al7f2ygp39des148iwzh'); +INSERT INTO `pear_task_stages_template` VALUES (78, '已完成&归档', 'd85f1bvwpml2nhxe92zu7tyi', '2018-12-24 22:02:45', 0, 'vn6dxyzme1g8ucbl3ikq0awt'); +INSERT INTO `pear_task_stages_template` VALUES (79, '产品计划', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:03', 0, '3atxfsv5rhz64pk8jl0enqd2'); +INSERT INTO `pear_task_stages_template` VALUES (80, '即将发布', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:09', 0, '1nucptea9b2vl7yfj8xgz4d6'); +INSERT INTO `pear_task_stages_template` VALUES (81, '测试', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:13', 0, 'pfidejaq2vn653h8zmsytrlb'); +INSERT INTO `pear_task_stages_template` VALUES (82, '准备发布', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:17', 0, 'uc1etmw4k5gys8jfpdbo7zrh'); +INSERT INTO `pear_task_stages_template` VALUES (83, '发布成功', 'd85f1bvwpml2nhxe94zu7tyi', '2018-12-24 22:06:23', 0, 'rmutqozd51shfp4w70n96iel'); + +-- ---------------------------- +-- Table structure for pear_task_tag +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_tag`; +CREATE TABLE `pear_task_tag` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `project_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '项目id', + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签名', + `color` enum('blue','red','orange','green','brown','purple') CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT 'blue' COMMENT '颜色', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `color_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 11 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of pear_task_tag +-- ---------------------------- +INSERT INTO `pear_task_tag` VALUES (1, NULL, '1292', 'dd', 'orange', '2018-07-27 16:06:08', '#ff9900'); +INSERT INTO `pear_task_tag` VALUES (2, NULL, '1292', 'ffs', 'green', '2018-07-27 16:06:14', '#19be6b'); +INSERT INTO `pear_task_tag` VALUES (3, NULL, '1292', '11', 'blue', '2018-09-07 23:22:11', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (4, NULL, '1292', '22', 'blue', '2018-09-07 23:22:12', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (5, NULL, '1292', '33', 'blue', '2018-09-07 23:22:13', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (6, NULL, '1292', '44', 'blue', '2018-09-07 23:22:14', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (7, NULL, '1292', '55', 'blue', '2018-09-07 23:22:15', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (8, NULL, '1292', '6666666666666', 'blue', '2018-09-07 23:22:17', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (9, NULL, '1292', '777777777', 'blue', '2018-09-07 23:22:19', '#2d8cf0'); +INSERT INTO `pear_task_tag` VALUES (10, NULL, '1292', '8888888', 'blue', '2018-09-07 23:22:22', '#2d8cf0'); + +-- ---------------------------- +-- Table structure for pear_task_to_tag +-- ---------------------------- +DROP TABLE IF EXISTS `pear_task_to_tag`; +CREATE TABLE `pear_task_to_tag` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + `code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `task_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `tag_code` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT '0', + `create_time` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = MyISAM AUTO_INCREMENT = 95 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Fixed; + +-- ---------------------------- +-- Table structure for pear_user_token +-- ---------------------------- +DROP TABLE IF EXISTS `pear_user_token`; +CREATE TABLE `pear_user_token` ( + `token_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '令牌编号', + `user_id` int(10) UNSIGNED NOT NULL COMMENT '用户编号', + `user_name` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '用户名', + `token` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '登录令牌', + `login_time` int(10) UNSIGNED NOT NULL COMMENT '登录时间', + `client_type` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '客户端类型 android wap', + `login_ip` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '登录ip', + PRIMARY KEY (`token_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = 'PC端登录令牌表' ROW_FORMAT = Compact; + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/extend/controller/BasicApi.php b/extend/controller/BasicApi.php new file mode 100644 index 0000000..bc69c62 --- /dev/null +++ b/extend/controller/BasicApi.php @@ -0,0 +1,80 @@ +request = app('request'); +// try { +// ApiService::write(); +// } catch (\Exception $e) { +// $this->error($e->getMessage(), $e->getCode());; +// } + } + + /** + * 返回成功的操作 + * @param mixed $msg 消息内容 + * @param array $data 返回数据 + * @param integer $code 返回代码 + */ + protected function success($msg = '', $data = [], $code = 200) + { + ToolsService::success($msg, $data, $code); + } + + /** + * 返回失败的请求 + * @param mixed $msg 消息内容 + * @param array $data 返回数据 + * @param integer $code 返回代码 + */ + protected function error($msg, $code = 201, $data = []) + { + ToolsService::error($msg, $data, $code); + } + + /** + * 返回资源不存在 + * @param integer $code 返回代码 + */ + protected function notAllow($code = 403) + { + ToolsService::error('', [], $code); + } + + /** + * 返回资源不存在 + * @param integer $code 返回代码 + */ + protected function notFound($code = 404) + { + ToolsService::error('', [], $code); + } + +} diff --git a/extend/service/ApiService.php b/extend/service/ApiService.php new file mode 100644 index 0000000..d2489c3 --- /dev/null +++ b/extend/service/ApiService.php @@ -0,0 +1,99 @@ +where(['node' => $node])->field("title")->find(); + if ($nodeName) { + $nodeName = $nodeName['title']; + } + $data = [ + 'ip' => $ip, + 'node' => $node, + 'node_name' => $nodeName, + 'action' => $action, + 'action_name' => $actionName, + 'action_id' => $actionId, + 'content' => $content, + 'module' => $module, + 'from' => $from, + 'user_agent' => $header['user-agent'], + 'seconds' => time(), + ]; + return self::db()->insert($data) !== false; + } + + /** + * 流控策略 + * @param $node + * @param string $ip + * @throws \Exception + */ + public static function checkTraffic($node, $ip = '') + { + $where = ['node' => $node, 'seconds' => time()]; + if ($ip) { + $where['ip'] = $ip; + } + $count = self::db()->where($where)->count(); + if ($count >= 10) { + throw new \Exception('访问过于频繁', 1); + } + } + +} diff --git a/extend/service/DataService.php b/extend/service/DataService.php new file mode 100644 index 0000000..43a2406 --- /dev/null +++ b/extend/service/DataService.php @@ -0,0 +1,102 @@ + $sequence, 'type' => strtoupper($type)]; + return Db::name('SystemSequence')->where($data)->delete(); + } + + /** + * 生成唯一序号 (失败返回 NULL ) + * @param int $length 序号长度 + * @param string $type 序号顾类型 + * @return string + */ + public static function createSequence($length = 10, $type = 'SYSTEM') + { + $times = 0; + while ($times++ < 10) { + list($i, $sequence) = [0, '']; + while ($i++ < $length) { + $sequence .= ($i <= 1 ? rand(1, 9) : rand(0, 9)); + } + $data = ['sequence' => $sequence, 'type' => strtoupper($type)]; + if (Db::name('SystemSequence')->where($data)->count() < 1 && Db::name('SystemSequence')->insert($data) !== false) { + return $sequence; + } + } + return null; + } + + /** + * 数据增量保存 + * @param Query|string $dbQuery 数据查询对象 + * @param array $data 需要保存或更新的数据 + * @param string $key 条件主键限制 + * @param array $where 其它的where条件 + * @return bool + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function save($dbQuery, $data, $key = 'id', $where = []) + { + $db = is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery; + list($table, $map) = [$db->getTable(), [$key => isset($data[$key]) ? $data[$key] : '']]; + if (Db::table($table)->where($where)->where($map)->count() > 0) { + return Db::table($table)->strict(false)->where($where)->where($map)->update($data) !== false; + } + return Db::table($table)->strict(false)->insert($data) !== false; + } + + /** + * 更新数据表内容 + * @param Query|string $dbQuery 数据查询对象 + * @param array $where 额外查询条件 + * @return bool|null + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function update(&$dbQuery, $where = []) + { + $request = app('request'); + $db = is_string($dbQuery) ? Db::name($dbQuery) : $dbQuery; + list($pk, $table, $map) = [$db->getPk(), $db->getTable(), []]; + list($field, $value) = [$request->post('field', ''), $request->post('value', '')]; + $map[] = [empty($pk) ? 'id' : $pk, 'in', explode(',', $request->post('id', ''))]; + // 删除模式,如果存在 is_deleted 字段使用软删除 + if ($field === 'delete') { + if (method_exists($db, 'getTableFields') && in_array('is_deleted', $db->getTableFields())) { + return Db::table($table)->where($where)->where($map)->update(['is_deleted' => '1']) !== false; + } + return Db::table($table)->where($where)->where($map)->delete() !== false; + } + // 更新模式,更新指定字段内容 + return Db::table($table)->where($where)->where($map)->update([$field => $value]) !== false; + } + +} diff --git a/extend/service/DateService.php b/extend/service/DateService.php new file mode 100644 index 0000000..9eac23d --- /dev/null +++ b/extend/service/DateService.php @@ -0,0 +1,245 @@ +. + * + * @param string $remote timezone that to find the offset of + * @param string $local timezone used as the baseline + * @param mixed $now UNIX timestamp or date string + * @return integer + */ + public static function offset($remote, $local = NULL, $now = NULL) + { + if ($local === NULL) { + // Use the default timezone + $local = date_default_timezone_get(); + } + if (is_int($now)) { + // Convert the timestamp into a string + $now = date(DateTime::RFC2822, $now); + } + // Create timezone objects + $zone_remote = new DateTimeZone($remote); + $zone_local = new DateTimeZone($local); + // Create date objects from timezones + $time_remote = new DateTime($now, $zone_remote); + $time_local = new DateTime($now, $zone_local); + // Find the offset + $offset = $zone_remote->getOffset($time_remote) - $zone_local->getOffset($time_local); + return $offset; + } + + /** + * 计算两个时间戳之间相差的时间 + * + * $span = self::span(60, 182, 'minutes,seconds'); // array('minutes' => 2, 'seconds' => 2) + * $span = self::span(60, 182, 'minutes'); // 2 + * + * @param int $remote timestamp to find the span of + * @param int $local timestamp to use as the baseline + * @param string $output formatting string + * @return string when only a single output is requested + * @return array associative list of all outputs requested + * @from https://github.com/kohana/ohanzee-helpers/blob/master/src/Date.php + */ + public static function span($remote, $local = NULL, $output = 'years,months,weeks,days,hours,minutes,seconds') + { + // Normalize output + $output = trim(strtolower((string)$output)); + if (!$output) { + // Invalid output + return FALSE; + } + // Array with the output formats + $output = preg_split('/[^a-z]+/', $output); + // Convert the list of outputs to an associative array + $output = array_combine($output, array_fill(0, count($output), 0)); + // Make the output values into keys + extract(array_flip($output), EXTR_SKIP); + if ($local === NULL) { + // Calculate the span from the current time + $local = time(); + } + // Calculate timespan (seconds) + $timespan = abs($remote - $local); + if (isset($output['years'])) { + $timespan -= self::YEAR * ($output['years'] = (int)floor($timespan / self::YEAR)); + } + if (isset($output['months'])) { + $timespan -= self::MONTH * ($output['months'] = (int)floor($timespan / self::MONTH)); + } + if (isset($output['weeks'])) { + $timespan -= self::WEEK * ($output['weeks'] = (int)floor($timespan / self::WEEK)); + } + if (isset($output['days'])) { + $timespan -= self::DAY * ($output['days'] = (int)floor($timespan / self::DAY)); + } + if (isset($output['hours'])) { + $timespan -= self::HOUR * ($output['hours'] = (int)floor($timespan / self::HOUR)); + } + if (isset($output['minutes'])) { + $timespan -= self::MINUTE * ($output['minutes'] = (int)floor($timespan / self::MINUTE)); + } + // Seconds ago, 1 + if (isset($output['seconds'])) { + $output['seconds'] = $timespan; + } + if (count($output) === 1) { + // Only a single output was requested, return it + return array_pop($output); + } + // Return array + return $output; + } + + /** + * 格式化 UNIX 时间戳为人易读的字符串 + * + * @param int Unix 时间戳 + * @param mixed $local 本地时间 + * + * @return string 格式化的日期字符串 + */ + public static function human($remote, $local = null) + { + $timediff = (is_null($local) || $local ? time() : $local) - $remote; + $chunks = array( + array(60 * 60 * 24 * 365, 'year'), + array(60 * 60 * 24 * 30, 'month'), + array(60 * 60 * 24 * 7, 'week'), + array(60 * 60 * 24, 'day'), + array(60 * 60, 'hour'), + array(60, 'minute'), + array(1, 'second') + ); + + for ($i = 0, $j = count($chunks); $i < $j; $i++) { + $seconds = $chunks[$i][0]; + $name = $chunks[$i][1]; + if (($count = floor($timediff / $seconds)) != 0) { + break; + } + } + return __("%d {$name}%s ago", $count, ($count > 1 ? 's' : '')); + } + + /** + * 获取一个基于时间偏移的Unix时间戳 + * + * @param string $type 时间类型,默认为day,可选minute,hour,day,week,month,quarter,year + * @param int $offset 时间偏移量 默认为0,正数表示当前type之后,负数表示当前type之前 + * @param string $position 时间的开始或结束,默认为begin,可选前(begin,start,first,front),end + * @param int $year 基准年,默认为null,即以当前年为基准 + * @param int $month 基准月,默认为null,即以当前月为基准 + * @param int $day 基准天,默认为null,即以当前天为基准 + * @param int $hour 基准小时,默认为null,即以当前年小时基准 + * @param int $minute 基准分钟,默认为null,即以当前分钟为基准 + * @return int 处理后的Unix时间戳 + */ + public static function unixtime($type = 'day', $offset = 0, $position = 'begin', $year = null, $month = null, $day = null, $hour = null, $minute = null) + { + $year = is_null($year) ? date('Y') : $year; + $month = is_null($month) ? date('m') : $month; + $day = is_null($day) ? date('d') : $day; + $hour = is_null($hour) ? date('H') : $hour; + $minute = is_null($minute) ? date('i') : $minute; + $position = in_array($position, array('begin', 'start', 'first', 'front')); + + switch ($type) { + case 'minute': + $time = $position ? mktime($hour, $minute + $offset, 0, $month, $day, $year) : mktime($hour, $minute + $offset, 59, $month, $day, $year); + break; + case 'hour': + $time = $position ? mktime($hour + $offset, 0, 0, $month, $day, $year) : mktime($hour + $offset, 59, 59, $month, $day, $year); + break; + case 'day': + $time = $position ? mktime(0, 0, 0, $month, $day + $offset, $year) : mktime(23, 59, 59, $month, $day + $offset, $year); + break; + case 'week': + $time = $position ? + mktime(0, 0, 0, $month, $day - date("w", mktime(0, 0, 0, $month, $day, $year)) + 1 - 7 * (-$offset), $year) : + mktime(23, 59, 59, $month, $day - date("w", mktime(0, 0, 0, $month, $day, $year)) + 7 - 7 * (-$offset), $year); + break; + case 'month': + $time = $position ? mktime(0, 0, 0, $month + $offset, 1, $year) : mktime(23, 59, 59, $month + $offset, cal_days_in_month(CAL_GREGORIAN, $month + $offset, $year), $year); + break; + case 'quarter': + $time = $position ? + mktime(0, 0, 0, 1 + ((ceil(date('n', mktime(0, 0, 0, $month, $day, $year)) / 3) + $offset) - 1) * 3, 1, $year) : + mktime(23, 59, 59, (ceil(date('n', mktime(0, 0, 0, $month, $day, $year)) / 3) + $offset) * 3, cal_days_in_month(CAL_GREGORIAN, (ceil(date('n', mktime(0, 0, 0, $month, $day, $year)) / 3) + $offset) * 3, $year), $year); + break; + case 'year': + $time = $position ? mktime(0, 0, 0, 1, 1, $year + $offset) : mktime(23, 59, 59, 12, 31, $year + $offset); + break; + default: + $time = mktime($hour, $minute, 0, $month, $day, $year); + break; + } + return $time; + } + + /** + * 获取指定日期段内每一天的日期 + * @param string $startDate 开始日期 + * @param string $endDate 结束日期 + * @return array + */ + public static function getDateFromRange($startDate, $endDate) + { + + $startTimestamp = strtotime($startDate); + $endTimestamp = strtotime($endDate); + // 计算日期段内有多少天 + $days = floor(($endTimestamp - $startTimestamp) / 86400 + 1); + // 保存每天日期 + $date = array(); + + for ($i = 0; $i < $days; $i++) { + $date[] = date('Y-m-d', $startTimestamp + (86400 * $i)); + } + return $date; + } + + /** + * 获取某个月的周几日期列表 + * @param string $month 2018-10 + * @param string $day 0,1,2,3,4,5,6 + * @return array + */ + public static function getMonthDay($month = '', $day = '1') + { + if (empty($month)) { + $month = date("Y-m"); + } + $maxDay = date('t', strtotime($month . "-0" . $day)); + $mondays = array(); + for ($i = 1; $i <= $maxDay; $i++) { + if (date('w', strtotime($month . "-" . $i)) == $day) { + $mondays[] = $month . "-" . ($i > 9 ? '' : '0') . $i; + } + } + return $mondays; + } + + +} diff --git a/extend/service/DesService.php b/extend/service/DesService.php new file mode 100644 index 0000000..7f7e805 --- /dev/null +++ b/extend/service/DesService.php @@ -0,0 +1,296 @@ + 0 && $expire < time()) { + return ''; + } + $data = substr($data, 10); + return $data; + } + + /** + * Des算法 + * @param string $str 字符串 + * @param string $key 加密key + * @return string + */ + private static function _des($key, $message, $encrypt, $mode = 0, $iv = null) + { + //declaring this locally speeds things up a bit + $spfunction1 = array(0x1010400, 0, 0x10000, 0x1010404, 0x1010004, 0x10404, 0x4, 0x10000, 0x400, 0x1010400, 0x1010404, 0x400, 0x1000404, 0x1010004, 0x1000000, 0x4, 0x404, 0x1000400, 0x1000400, 0x10400, 0x10400, 0x1010000, 0x1010000, 0x1000404, 0x10004, 0x1000004, 0x1000004, 0x10004, 0, 0x404, 0x10404, 0x1000000, 0x10000, 0x1010404, 0x4, 0x1010000, 0x1010400, 0x1000000, 0x1000000, 0x400, 0x1010004, 0x10000, 0x10400, 0x1000004, 0x400, 0x4, 0x1000404, 0x10404, 0x1010404, 0x10004, 0x1010000, 0x1000404, 0x1000004, 0x404, 0x10404, 0x1010400, 0x404, 0x1000400, 0x1000400, 0, 0x10004, 0x10400, 0, 0x1010004); + $spfunction2 = array(-0x7fef7fe0, -0x7fff8000, 0x8000, 0x108020, 0x100000, 0x20, -0x7fefffe0, -0x7fff7fe0, -0x7fffffe0, -0x7fef7fe0, -0x7fef8000, -0x80000000, -0x7fff8000, 0x100000, 0x20, -0x7fefffe0, 0x108000, 0x100020, -0x7fff7fe0, 0, -0x80000000, 0x8000, 0x108020, -0x7ff00000, 0x100020, -0x7fffffe0, 0, 0x108000, 0x8020, -0x7fef8000, -0x7ff00000, 0x8020, 0, 0x108020, -0x7fefffe0, 0x100000, -0x7fff7fe0, -0x7ff00000, -0x7fef8000, 0x8000, -0x7ff00000, -0x7fff8000, 0x20, -0x7fef7fe0, 0x108020, 0x20, 0x8000, -0x80000000, 0x8020, -0x7fef8000, 0x100000, -0x7fffffe0, 0x100020, -0x7fff7fe0, -0x7fffffe0, 0x100020, 0x108000, 0, -0x7fff8000, 0x8020, -0x80000000, -0x7fefffe0, -0x7fef7fe0, 0x108000); + $spfunction3 = array(0x208, 0x8020200, 0, 0x8020008, 0x8000200, 0, 0x20208, 0x8000200, 0x20008, 0x8000008, 0x8000008, 0x20000, 0x8020208, 0x20008, 0x8020000, 0x208, 0x8000000, 0x8, 0x8020200, 0x200, 0x20200, 0x8020000, 0x8020008, 0x20208, 0x8000208, 0x20200, 0x20000, 0x8000208, 0x8, 0x8020208, 0x200, 0x8000000, 0x8020200, 0x8000000, 0x20008, 0x208, 0x20000, 0x8020200, 0x8000200, 0, 0x200, 0x20008, 0x8020208, 0x8000200, 0x8000008, 0x200, 0, 0x8020008, 0x8000208, 0x20000, 0x8000000, 0x8020208, 0x8, 0x20208, 0x20200, 0x8000008, 0x8020000, 0x8000208, 0x208, 0x8020000, 0x20208, 0x8, 0x8020008, 0x20200); + $spfunction4 = array(0x802001, 0x2081, 0x2081, 0x80, 0x802080, 0x800081, 0x800001, 0x2001, 0, 0x802000, 0x802000, 0x802081, 0x81, 0, 0x800080, 0x800001, 0x1, 0x2000, 0x800000, 0x802001, 0x80, 0x800000, 0x2001, 0x2080, 0x800081, 0x1, 0x2080, 0x800080, 0x2000, 0x802080, 0x802081, 0x81, 0x800080, 0x800001, 0x802000, 0x802081, 0x81, 0, 0, 0x802000, 0x2080, 0x800080, 0x800081, 0x1, 0x802001, 0x2081, 0x2081, 0x80, 0x802081, 0x81, 0x1, 0x2000, 0x800001, 0x2001, 0x802080, 0x800081, 0x2001, 0x2080, 0x800000, 0x802001, 0x80, 0x800000, 0x2000, 0x802080); + $spfunction5 = array(0x100, 0x2080100, 0x2080000, 0x42000100, 0x80000, 0x100, 0x40000000, 0x2080000, 0x40080100, 0x80000, 0x2000100, 0x40080100, 0x42000100, 0x42080000, 0x80100, 0x40000000, 0x2000000, 0x40080000, 0x40080000, 0, 0x40000100, 0x42080100, 0x42080100, 0x2000100, 0x42080000, 0x40000100, 0, 0x42000000, 0x2080100, 0x2000000, 0x42000000, 0x80100, 0x80000, 0x42000100, 0x100, 0x2000000, 0x40000000, 0x2080000, 0x42000100, 0x40080100, 0x2000100, 0x40000000, 0x42080000, 0x2080100, 0x40080100, 0x100, 0x2000000, 0x42080000, 0x42080100, 0x80100, 0x42000000, 0x42080100, 0x2080000, 0, 0x40080000, 0x42000000, 0x80100, 0x2000100, 0x40000100, 0x80000, 0, 0x40080000, 0x2080100, 0x40000100); + $spfunction6 = array(0x20000010, 0x20400000, 0x4000, 0x20404010, 0x20400000, 0x10, 0x20404010, 0x400000, 0x20004000, 0x404010, 0x400000, 0x20000010, 0x400010, 0x20004000, 0x20000000, 0x4010, 0, 0x400010, 0x20004010, 0x4000, 0x404000, 0x20004010, 0x10, 0x20400010, 0x20400010, 0, 0x404010, 0x20404000, 0x4010, 0x404000, 0x20404000, 0x20000000, 0x20004000, 0x10, 0x20400010, 0x404000, 0x20404010, 0x400000, 0x4010, 0x20000010, 0x400000, 0x20004000, 0x20000000, 0x4010, 0x20000010, 0x20404010, 0x404000, 0x20400000, 0x404010, 0x20404000, 0, 0x20400010, 0x10, 0x4000, 0x20400000, 0x404010, 0x4000, 0x400010, 0x20004010, 0, 0x20404000, 0x20000000, 0x400010, 0x20004010); + $spfunction7 = array(0x200000, 0x4200002, 0x4000802, 0, 0x800, 0x4000802, 0x200802, 0x4200800, 0x4200802, 0x200000, 0, 0x4000002, 0x2, 0x4000000, 0x4200002, 0x802, 0x4000800, 0x200802, 0x200002, 0x4000800, 0x4000002, 0x4200000, 0x4200800, 0x200002, 0x4200000, 0x800, 0x802, 0x4200802, 0x200800, 0x2, 0x4000000, 0x200800, 0x4000000, 0x200800, 0x200000, 0x4000802, 0x4000802, 0x4200002, 0x4200002, 0x2, 0x200002, 0x4000000, 0x4000800, 0x200000, 0x4200800, 0x802, 0x200802, 0x4200800, 0x802, 0x4000002, 0x4200802, 0x4200000, 0x200800, 0, 0x2, 0x4200802, 0, 0x200802, 0x4200000, 0x800, 0x4000002, 0x4000800, 0x800, 0x200002); + $spfunction8 = array(0x10001040, 0x1000, 0x40000, 0x10041040, 0x10000000, 0x10001040, 0x40, 0x10000000, 0x40040, 0x10040000, 0x10041040, 0x41000, 0x10041000, 0x41040, 0x1000, 0x40, 0x10040000, 0x10000040, 0x10001000, 0x1040, 0x41000, 0x40040, 0x10040040, 0x10041000, 0x1040, 0, 0, 0x10040040, 0x10000040, 0x10001000, 0x41040, 0x40000, 0x41040, 0x40000, 0x10041000, 0x1000, 0x40, 0x10040040, 0x1000, 0x41040, 0x10001000, 0x40, 0x10000040, 0x10040000, 0x10040040, 0x10000000, 0x40000, 0x10001040, 0, 0x10041040, 0x40040, 0x10000040, 0x10040000, 0x10001000, 0x10001040, 0, 0x10041040, 0x41000, 0x41000, 0x1040, 0x1040, 0x40040, 0x10000000, 0x10041000); + $masks = array(4294967295, 2147483647, 1073741823, 536870911, 268435455, 134217727, 67108863, 33554431, 16777215, 8388607, 4194303, 2097151, 1048575, 524287, 262143, 131071, 65535, 32767, 16383, 8191, 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0); + + //create the 16 or 48 subkeys we will need + $keys = self::_createKeys($key); + $m = 0; + $len = strlen($message); + $chunk = 0; + //set up the loops for single and triple des + $iterations = ((count($keys) == 32) ? 3 : 9); //single or triple des + if (3 == $iterations) {$looping = (($encrypt) ? array(0, 32, 2) : array(30, -2, -2));} else { $looping = (($encrypt) ? array(0, 32, 2, 62, 30, -2, 64, 96, 2) : array(94, 62, -2, 32, 64, 2, 30, -2, -2));} + + $message .= (chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0) . chr(0)); //pad the message out with null bytes + //store the result here + $result = ""; + $tempresult = ""; + + if (1 == $mode) { + //CBC mode + $cbcleft = (ord($iv{$m++}) << 24) | (ord($iv{$m++}) << 16) | (ord($iv{$m++}) << 8) | ord($iv{$m++}); + $cbcright = (ord($iv{$m++}) << 24) | (ord($iv{$m++}) << 16) | (ord($iv{$m++}) << 8) | ord($iv{$m++}); + $m = 0; + } + + //loop through each 64 bit chunk of the message + while ($m < $len) { + $left = (ord($message{$m++}) << 24) | (ord($message{$m++}) << 16) | (ord($message{$m++}) << 8) | ord($message{$m++}); + $right = (ord($message{$m++}) << 24) | (ord($message{$m++}) << 16) | (ord($message{$m++}) << 8) | ord($message{$m++}); + + //for Cipher Block Chaining mode, xor the message with the previous result + if (1 == $mode) { + if ($encrypt) {$left ^= $cbcleft; + $right ^= $cbcright;} else { + $cbcleft2 = $cbcleft; + $cbcright2 = $cbcright; + $cbcleft = $left; + $cbcright = $right;}} + + //first each 64 but chunk of the message must be permuted according to IP + $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f; + $right ^= $temp; + $left ^= ($temp << 4); + $temp = (($left >> 16 & $masks[16]) ^ $right) & 0x0000ffff; + $right ^= $temp; + $left ^= ($temp << 16); + $temp = (($right >> 2 & $masks[2]) ^ $left) & 0x33333333; + $left ^= $temp; + $right ^= ($temp << 2); + $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff; + $left ^= $temp; + $right ^= ($temp << 8); + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; + $right ^= $temp; + $left ^= ($temp << 1); + + $left = (($left << 1) | ($left >> 31 & $masks[31])); + $right = (($right << 1) | ($right >> 31 & $masks[31])); + + //do this either 1 or 3 times for each chunk of the message + for ($j = 0; $j < $iterations; $j += 3) { + $endloop = $looping[$j + 1]; + $loopinc = $looping[$j + 2]; + //now go through and perform the encryption or decryption + for ($i = $looping[$j]; $i != $endloop; $i += $loopinc) { + //for efficiency + $right1 = $right ^ $keys[$i]; + $right2 = (($right >> 4 & $masks[4]) | ($right << 28)) ^ $keys[$i + 1]; + //the result is attained by passing these bytes through the S selection functions + $temp = $left; + $left = $right; + $right = $temp ^ ($spfunction2[($right1 >> 24 & $masks[24]) & 0x3f] | $spfunction4[($right1 >> 16 & $masks[16]) & 0x3f] + | $spfunction6[($right1 >> 8 & $masks[8]) & 0x3f] | $spfunction8[$right1 & 0x3f] + | $spfunction1[($right2 >> 24 & $masks[24]) & 0x3f] | $spfunction3[($right2 >> 16 & $masks[16]) & 0x3f] + | $spfunction5[($right2 >> 8 & $masks[8]) & 0x3f] | $spfunction7[$right2 & 0x3f]); + } + $temp = $left; + $left = $right; + $right = $temp; //unreverse left and right + } //for either 1 or 3 iterations + + //move then each one bit to the right + $left = (($left >> 1 & $masks[1]) | ($left << 31)); + $right = (($right >> 1 & $masks[1]) | ($right << 31)); + + //now perform IP-1, which is IP in the opposite direction + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; + $right ^= $temp; + $left ^= ($temp << 1); + $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff; + $left ^= $temp; + $right ^= ($temp << 8); + $temp = (($right >> 2 & $masks[2]) ^ $left) & 0x33333333; + $left ^= $temp; + $right ^= ($temp << 2); + $temp = (($left >> 16 & $masks[16]) ^ $right) & 0x0000ffff; + $right ^= $temp; + $left ^= ($temp << 16); + $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f; + $right ^= $temp; + $left ^= ($temp << 4); + + //for Cipher Block Chaining mode, xor the message with the previous result + if (1 == $mode) { + if ($encrypt) {$cbcleft = $left; + $cbcright = $right;} else { + $left ^= $cbcleft2; + $right ^= $cbcright2;}} + $tempresult .= (chr($left >> 24 & $masks[24]) . chr(($left >> 16 & $masks[16]) & 0xff) . chr(($left >> 8 & $masks[8]) & 0xff) . chr($left & 0xff) . chr($right >> 24 & $masks[24]) . chr(($right >> 16 & $masks[16]) & 0xff) . chr(($right >> 8 & $masks[8]) & 0xff) . chr($right & 0xff)); + + $chunk += 8; + if (512 == $chunk) { + $result .= $tempresult; + $tempresult = ""; + $chunk = 0;} + } //for every 8 characters, or 64 bits in the message + + //return the result as an array + return ($result . $tempresult); + } //end of des + + /** + * createKeys + * this takes as input a 64 bit key (even though only 56 bits are used) + * as an array of 2 integers, and returns 16 48 bit keys + * @param string $key 加密key + * @return string + */ + private static function _createKeys($key) + { + //declaring this locally speeds things up a bit + $pc2bytes0 = array(0, 0x4, 0x20000000, 0x20000004, 0x10000, 0x10004, 0x20010000, 0x20010004, 0x200, 0x204, 0x20000200, 0x20000204, 0x10200, 0x10204, 0x20010200, 0x20010204); + $pc2bytes1 = array(0, 0x1, 0x100000, 0x100001, 0x4000000, 0x4000001, 0x4100000, 0x4100001, 0x100, 0x101, 0x100100, 0x100101, 0x4000100, 0x4000101, 0x4100100, 0x4100101); + $pc2bytes2 = array(0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808, 0, 0x8, 0x800, 0x808, 0x1000000, 0x1000008, 0x1000800, 0x1000808); + $pc2bytes3 = array(0, 0x200000, 0x8000000, 0x8200000, 0x2000, 0x202000, 0x8002000, 0x8202000, 0x20000, 0x220000, 0x8020000, 0x8220000, 0x22000, 0x222000, 0x8022000, 0x8222000); + $pc2bytes4 = array(0, 0x40000, 0x10, 0x40010, 0, 0x40000, 0x10, 0x40010, 0x1000, 0x41000, 0x1010, 0x41010, 0x1000, 0x41000, 0x1010, 0x41010); + $pc2bytes5 = array(0, 0x400, 0x20, 0x420, 0, 0x400, 0x20, 0x420, 0x2000000, 0x2000400, 0x2000020, 0x2000420, 0x2000000, 0x2000400, 0x2000020, 0x2000420); + $pc2bytes6 = array(0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002, 0, 0x10000000, 0x80000, 0x10080000, 0x2, 0x10000002, 0x80002, 0x10080002); + $pc2bytes7 = array(0, 0x10000, 0x800, 0x10800, 0x20000000, 0x20010000, 0x20000800, 0x20010800, 0x20000, 0x30000, 0x20800, 0x30800, 0x20020000, 0x20030000, 0x20020800, 0x20030800); + $pc2bytes8 = array(0, 0x40000, 0, 0x40000, 0x2, 0x40002, 0x2, 0x40002, 0x2000000, 0x2040000, 0x2000000, 0x2040000, 0x2000002, 0x2040002, 0x2000002, 0x2040002); + $pc2bytes9 = array(0, 0x10000000, 0x8, 0x10000008, 0, 0x10000000, 0x8, 0x10000008, 0x400, 0x10000400, 0x408, 0x10000408, 0x400, 0x10000400, 0x408, 0x10000408); + $pc2bytes10 = array(0, 0x20, 0, 0x20, 0x100000, 0x100020, 0x100000, 0x100020, 0x2000, 0x2020, 0x2000, 0x2020, 0x102000, 0x102020, 0x102000, 0x102020); + $pc2bytes11 = array(0, 0x1000000, 0x200, 0x1000200, 0x200000, 0x1200000, 0x200200, 0x1200200, 0x4000000, 0x5000000, 0x4000200, 0x5000200, 0x4200000, 0x5200000, 0x4200200, 0x5200200); + $pc2bytes12 = array(0, 0x1000, 0x8000000, 0x8001000, 0x80000, 0x81000, 0x8080000, 0x8081000, 0x10, 0x1010, 0x8000010, 0x8001010, 0x80010, 0x81010, 0x8080010, 0x8081010); + $pc2bytes13 = array(0, 0x4, 0x100, 0x104, 0, 0x4, 0x100, 0x104, 0x1, 0x5, 0x101, 0x105, 0x1, 0x5, 0x101, 0x105); + $masks = array(4294967295, 2147483647, 1073741823, 536870911, 268435455, 134217727, 67108863, 33554431, 16777215, 8388607, 4194303, 2097151, 1048575, 524287, 262143, 131071, 65535, 32767, 16383, 8191, 4095, 2047, 1023, 511, 255, 127, 63, 31, 15, 7, 3, 1, 0); + + //how many iterations (1 for des, 3 for triple des) + $iterations = ((strlen($key) >= 24) ? 3 : 1); + //stores the return keys + $keys = array(); // size = 32 * iterations but you don't specify this in php + //now define the left shifts which need to be done + $shifts = array(0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0); + //other variables + $m = 0; + $n = 0; + + for ($j = 0; $j < $iterations; $j++) { + //either 1 or 3 iterations + $left = (ord($key{$m++}) << 24) | (ord($key{$m++}) << 16) | (ord($key{$m++}) << 8) | ord($key{$m++}); + $right = (ord($key{$m++}) << 24) | (ord($key{$m++}) << 16) | (ord($key{$m++}) << 8) | ord($key{$m++}); + + $temp = (($left >> 4 & $masks[4]) ^ $right) & 0x0f0f0f0f; + $right ^= $temp; + $left ^= ($temp << 4); + $temp = (($right >> 16 & $masks[16]) ^ $left) & 0x0000ffff; + $left ^= $temp; + $right ^= ($temp << -16); + $temp = (($left >> 2 & $masks[2]) ^ $right) & 0x33333333; + $right ^= $temp; + $left ^= ($temp << 2); + $temp = (($right >> 16 & $masks[16]) ^ $left) & 0x0000ffff; + $left ^= $temp; + $right ^= ($temp << -16); + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; + $right ^= $temp; + $left ^= ($temp << 1); + $temp = (($right >> 8 & $masks[8]) ^ $left) & 0x00ff00ff; + $left ^= $temp; + $right ^= ($temp << 8); + $temp = (($left >> 1 & $masks[1]) ^ $right) & 0x55555555; + $right ^= $temp; + $left ^= ($temp << 1); + + //the right side needs to be shifted and to get the last four bits of the left side + $temp = ($left << 8) | (($right >> 20 & $masks[20]) & 0x000000f0); + //left needs to be put upside down + $left = ($right << 24) | (($right << 8) & 0xff0000) | (($right >> 8 & $masks[8]) & 0xff00) | (($right >> 24 & $masks[24]) & 0xf0); + $right = $temp; + + //now go through and perform these shifts on the left and right keys + for ($i = 0; $i < count($shifts); $i++) { + //shift the keys either one or two bits to the left + if ($shifts[$i] > 0) { + $left = (($left << 2) | ($left >> 26 & $masks[26])); + $right = (($right << 2) | ($right >> 26 & $masks[26])); + } else { + $left = (($left << 1) | ($left >> 27 & $masks[27])); + $right = (($right << 1) | ($right >> 27 & $masks[27])); + } + $left = $left & -0xf; + $right = $right & -0xf; + + //now apply PC-2, in such a way that E is easier when encrypting or decrypting + //this conversion will look like PC-2 except only the last 6 bits of each byte are used + //rather than 48 consecutive bits and the order of lines will be according to + //how the S selection functions will be applied: S2, S4, S6, S8, S1, S3, S5, S7 + $lefttemp = $pc2bytes0[$left >> 28 & $masks[28]] | $pc2bytes1[($left >> 24 & $masks[24]) & 0xf] + | $pc2bytes2[($left >> 20 & $masks[20]) & 0xf] | $pc2bytes3[($left >> 16 & $masks[16]) & 0xf] + | $pc2bytes4[($left >> 12 & $masks[12]) & 0xf] | $pc2bytes5[($left >> 8 & $masks[8]) & 0xf] + | $pc2bytes6[($left >> 4 & $masks[4]) & 0xf]; + $righttemp = $pc2bytes7[$right >> 28 & $masks[28]] | $pc2bytes8[($right >> 24 & $masks[24]) & 0xf] + | $pc2bytes9[($right >> 20 & $masks[20]) & 0xf] | $pc2bytes10[($right >> 16 & $masks[16]) & 0xf] + | $pc2bytes11[($right >> 12 & $masks[12]) & 0xf] | $pc2bytes12[($right >> 8 & $masks[8]) & 0xf] + | $pc2bytes13[($right >> 4 & $masks[4]) & 0xf]; + $temp = (($righttemp >> 16 & $masks[16]) ^ $lefttemp) & 0x0000ffff; + $keys[$n++] = $lefttemp ^ $temp; + $keys[$n++] = $righttemp ^ ($temp << 16); + } + } //for each iterations + //return the keys we've created + return $keys; + } //end of des_createKeys + +} \ No newline at end of file diff --git a/extend/service/FileService.php b/extend/service/FileService.php new file mode 100644 index 0000000..afc1a4d --- /dev/null +++ b/extend/service/FileService.php @@ -0,0 +1,396 @@ +isSsl() ? 'https' : 'http'; + return "{$protocol}://" . sysconf('storage_oss_domain'); + } + + /** + * 获取服务器URL前缀 + * @return string + */ + public static function getBaseUriLocal() + { + $appRoot = request()->root(true); // 去掉参数 true 将获得相对地址 + $uriRoot = preg_match('/\.php$/', $appRoot) ? dirname($appRoot) : $appRoot; + $uriRoot = in_array($uriRoot, ['/', '\\']) ? '' : $uriRoot; + return "{$uriRoot}/"; + } + + /** + * 获取七牛云URL前缀 + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function getBaseUriQiniu() + { + switch (strtolower(sysconf('storage_qiniu_is_https'))) { + case 'https': + return 'https://' . sysconf('storage_qiniu_domain') . '/'; + case 'http': + return 'http://' . sysconf('storage_qiniu_domain') . '/'; + case 'auto': + return '//' . sysconf('storage_qiniu_domain') . '/'; + default: + throw new \think\Exception('未设置七牛云文件地址协议'); + } + } + + /** + * 获取阿里云对象存储URL前缀 + * @return string + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function getBaseUriOss() + { + switch (strtolower(sysconf('storage_oss_is_https'))) { + case 'https': + return 'https://' . sysconf('storage_oss_domain') . '/'; + case 'http': + return 'http://' . sysconf('storage_oss_domain') . '/'; + case 'auto': + return '//' . sysconf('storage_oss_domain') . '/'; + default: + throw new \think\Exception('未设置阿里云文件地址协议'); + } + } + + /** + * 获取文件相对名称 + * @param string $local_url 文件标识 + * @param string $ext 文件后缀 + * @param string $pre 文件前缀(若有值需要以/结尾) + * @return string + */ + public static function getFileName($local_url, $ext = '', $pre = '') + { + empty($ext) && $ext = strtolower(pathinfo($local_url, 4)); + return $pre . join('/', str_split(md5($local_url), 16)) . '.' . ($ext ? $ext : 'tmp'); + } + + public static function removeSuffix($fileName) + { + return basename($fileName, "." . substr(strrchr($fileName, '.'), 1)); + } + + /** + * 检查文件是否已经存在 + * @param string $filename + * @param string|null $storage + * @return bool + * @throws OssException + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function hasFile($filename, $storage = null) + { + switch (empty($storage) ? sysconf('storage_type') : $storage) { + case 'local': + return file_exists(env('root_path') . $filename); + case 'qiniu': + $auth = new Auth(sysconf('storage_qiniu_access_key'), sysconf('storage_qiniu_secret_key')); + $bucketMgr = new BucketManager($auth); + list($ret, $err) = $bucketMgr->stat(sysconf('storage_qiniu_bucket'), $filename); + return $err === null; + case 'oss': + $ossClient = new OssClient(sysconf('storage_oss_keyid'), sysconf('storage_oss_secret'), self::getBaseUriOss(), true); + return $ossClient->doesObjectExist(sysconf('storage_oss_bucket'), $filename); + } + return false; + } + + /** + * 根据Key读取文件内容 + * @param string $filename + * @param string|null $storage + * @return string|null + * @throws \think\Exception + * @throws \think\exception\PDOException + * @throws OssException + */ + public static function readFile($filename, $storage = null) + { + switch (empty($storage) ? sysconf('storage_type') : $storage) { + case 'local': + $file = env('root_path') . config('upload.base_path') . $filename; + return file_exists($file) ? file_get_contents($file) : ''; + case 'qiniu': + $auth = new Auth(sysconf('storage_qiniu_access_key'), sysconf('storage_qiniu_secret_key')); + return file_get_contents($auth->privateDownloadUrl(self::getBaseUriQiniu() . $filename)); + case 'oss': + $ossClient = new OssClient(sysconf('storage_oss_keyid'), sysconf('storage_oss_secret'), self::getBaseUriOss(), true); + return $ossClient->getObject(sysconf('storage_oss_bucket'), $filename); + default: + throw new \think\Exception('未配置读取文件的存储方法'); + } + } + + /** + * 根据当前配置存储文件 + * @param string $filename + * @param string $content + * @param string|null $file_storage + * @return array|false + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function save($filename, $content, $file_storage = null) + { + $type = empty($file_storage) ? sysconf('storage_type') : $file_storage; + if (!method_exists(__CLASS__, $type)) { + Log::error("保存存储失败,调用{$type}存储引擎不存在!"); + return false; + } + return self::$type($filename, $content); + } + + /** + * 文件储存在本地 + * @param string $filename + * @param string $content + * @return array|null + */ + public static function local($filename, $content) + { + try { + $realfile = env('root_path') . $filename; + !file_exists(dirname($realfile)) && mkdir(dirname($realfile), 0755, true); + if (file_put_contents($realfile, $content)) { + $url = pathinfo(request()->baseFile(true), PATHINFO_DIRNAME) . '/' . $filename; + return ['file' => $realfile, 'hash' => md5_file($realfile), 'key' => "{$filename}", 'url' => $url]; + } + } catch (Exception $err) { + Log::error('本地文件存储失败, ' . $err->getMessage()); + } + return null; + } + + /** + * 七牛云存储 + * @param string $filename + * @param string $content + * @return array|null + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function qiniu($filename, $content) + { + $auth = new Auth(sysconf('storage_qiniu_access_key'), sysconf('storage_qiniu_secret_key')); + $token = $auth->uploadToken(sysconf('storage_qiniu_bucket')); + $uploadMgr = new UploadManager(); + list($result, $err) = $uploadMgr->put($token, $filename, $content); + if ($err !== null) { + Log::error('七牛云文件上传失败, ' . $err->getMessage()); + return null; + } + $result['file'] = $filename; + $result['url'] = self::getBaseUriQiniu() . $filename; + return $result; + } + + /** + * 阿里云OSS + * @param string $filename + * @param string $content + * @return array|null + * @throws \think\Exception + * @throws \think\exception\PDOException + */ + public static function oss($filename, $content) + { + try { + $endpoint = 'http://' . sysconf('storage_oss_domain'); + $ossClient = new OssClient(sysconf('storage_oss_keyid'), sysconf('storage_oss_secret'), $endpoint, true); + $result = $ossClient->putObject(sysconf('storage_oss_bucket'), $filename, $content); + $baseUrl = explode('://', $result['oss-request-url'])[1]; + if (strtolower(sysconf('storage_oss_is_https')) === 'http') { + $site_url = "http://{$baseUrl}"; + } elseif (strtolower(sysconf('storage_oss_is_https')) === 'https') { + $site_url = "https://{$baseUrl}"; + } else { + $site_url = "//{$baseUrl}"; + } + return ['file' => $filename, 'hash' => $result['content-md5'], 'key' => $filename, 'url' => $site_url]; + } catch (OssException $err) { + Log::error('阿里云OSS文件上传失败, ' . $err->getMessage()); + } + return null; + } + + /** + * 下载文件到本地 + * @param string $url 文件URL地址 + * @param bool $isForce 是否强制重新下载文件 + * @return array + */ + public static function download($url, $isForce = false) + { + try { + $filename = self::getFileName($url, '', 'download/'); + if (false === $isForce && ($siteUrl = self::getFileUrl($filename, 'local'))) { + $realfile = env('root_path') . config('upload.base_path') . $filename; + return ['file' => $realfile, 'hash' => md5_file($realfile), 'key' => config('upload.base_path') . "{$filename}", 'url' => $siteUrl]; + } + return self::local($filename, file_get_contents($url)); + } catch (\Exception $e) { + Log::error("FileService 文件下载失败 [ {$url} ] " . $e->getMessage()); + } + return ['url' => $url]; + } + +} diff --git a/extend/service/HttpService.php b/extend/service/HttpService.php new file mode 100644 index 0000000..37ed7bb --- /dev/null +++ b/extend/service/HttpService.php @@ -0,0 +1,106 @@ + $value) { + if (is_string($value) && class_exists('CURLFile', false) && stripos($value, '@') === 0) { + if (($filename = realpath(trim($value, '@'))) && file_exists($filename)) { + list($needBuild, $data[$key]) = [false, new \CURLFile($filename)]; + } + } + } + return $needBuild ? http_build_query($data) : $data; + } +} diff --git a/extend/service/LogService.php b/extend/service/LogService.php new file mode 100644 index 0000000..fdb9456 --- /dev/null +++ b/extend/service/LogService.php @@ -0,0 +1,49 @@ +module(), $request->controller(), $request->action()])); + $data = [ + 'ip' => $request->ip(), + 'node' => $node, + 'action' => $action, + 'content' => $content, + 'username' => session('user.username') . '', + ]; + return self::db()->insert($data) !== false; + } + +} diff --git a/extend/service/MessageService.php b/extend/service/MessageService.php new file mode 100644 index 0000000..f4e80b1 --- /dev/null +++ b/extend/service/MessageService.php @@ -0,0 +1,81 @@ +messageFormat($message, $action), $client_id_array, $exclude_client_id, $raw); + } + + public function sendToUid($uid, $message, $action = '') + { + Gateway::sendToUid($uid, $this->messageFormat($message, $action)); + } + + public function sendToClient($client_id, $message, $action = '') + { + Gateway::sendToClient($client_id, $this->messageFormat($message, $action)); + } + + public function sendToGroup($group, $message, $action = '') + { + Gateway::sendToGroup($group, $this->messageFormat($message, $action)); + } + + public function bindUid($client_id, $uid) + { + Gateway::bindUid($client_id, $uid); + } + + public function joinGroup($client_id, $group) + { + if ($group) { + Gateway::joinGroup($client_id, $group); + } + } + + public function messageFormat($message, $action = 'none') + { + $messageData = [ + 'action' => $action, //推送场景 + 'msg' => '', //推送内容 + 'title' => '消息通知',//推送标题 + 'data' => [],//推送数据 + 'uid' => 0,//推送数据 + ]; + if (is_array($message)) { + $messageData['data'] = $message; + $messageData['msg'] = isset($message['content']) ? $message['content'] : ''; + $messageData['title'] = isset($message['title']) ? $message['title'] : ''; + } else { + $messageData['msg'] = $message; + } + return json_encode($messageData, JSON_UNESCAPED_UNICODE); + } + +} diff --git a/extend/service/NodeService.php b/extend/service/NodeService.php new file mode 100644 index 0000000..460bb0f --- /dev/null +++ b/extend/service/NodeService.php @@ -0,0 +1,170 @@ + '1']; + $authorizeIds = Db::name('ProjectAuth')->whereIn('id', explode(',', $authorize))->where($where)->column('id'); + if (empty($authorizeIds)) { + return session('member.nodes', []); + } + $nodes = Db::name('ProjectAuthNode')->whereIn('auth', $authorizeIds)->column('node'); + return session('member.nodes', $nodes); + } + return false; + } + + /** + * 获取项目账号授权节点 + * @return array + */ + public static function getProjectAuthNode() + { + $nodes = cache('member_need_access_node'); + if (empty($nodes)) { + $nodes = Db::name('ProjectNode')->where(['is_auth' => '1'])->column('node'); + cache('member_need_access_node', $nodes); + } + return $nodes; + } + + /** + * 检查账号节点权限 + * @param string $node 节点 + * @param $moduleApp string + * @return bool + */ + public static function checkAuthNode($node, $moduleApp = 'project') + { + list($module, $controller, $action) = explode('/', str_replace(['?', '=', '&'], '/', $node . '///')); + $currentNode = self::parseNodeStr("{$module}/{$controller}") . strtolower("/{$action}"); + if ($moduleApp == 'project') { + //拥有者账号不加入权限判断 + if (session('member.is_owner') == 1) { + return true; + } + if (!in_array($currentNode, self::getProjectAuthNode())) { + return true; + } + return in_array($currentNode, (array)session('member.nodes')); + } + return false; + } + + /** + * 获取系统代码节点 + * @param array $nodes + * @param array $where 查询条件 + * @param string $module 节点模块。目录名应该和模块名一致,如果传入模块,则只搜索该模块目录下的节点。 + * @return array + */ + public static function get($nodes = [], $where = [], $module = '') + { + if ($module == 'project') { + $alias = Db::name('ProjectNode')->where($where)->column('node,is_menu,is_auth,is_login,title,id'); + } + $ignore = ['index', 'api', 'project/login', 'project/register', 'project/getCaptcha']; + $path = env('app_path'); + if ($module) { + $path .= '/' . $module; + } + foreach (self::getNodeTree($path) as $thr) { + foreach ($ignore as $str) { + if (stripos($thr, $str) === 0) { + continue 2; + } + } + $tmp = explode('/', $thr); + list($one, $two) = ["{$tmp[0]}", "{$tmp[0]}/{$tmp[1]}"]; + $nodes[$one] = array_merge(isset($alias[$one]) ? $alias[$one] : ['node' => $one, 'title' => '', 'is_menu' => 0, 'is_auth' => 0, 'is_login' => 0], ['pnode' => '']); + $nodes[$two] = array_merge(isset($alias[$two]) ? $alias[$two] : ['node' => $two, 'title' => '', 'is_menu' => 0, 'is_auth' => 0, 'is_login' => 0], ['pnode' => $one]); + $nodes[$thr] = array_merge(isset($alias[$thr]) ? $alias[$thr] : ['node' => $thr, 'title' => '', 'is_menu' => 0, 'is_auth' => 0, 'is_login' => 0], ['pnode' => $two]); + } + foreach ($nodes as $key => &$node) { + list($node['is_auth'], $node['is_menu'], $node['is_login']) = [intval($node['is_auth']), intval($node['is_menu']), empty($node['is_auth']) ? intval($node['is_login']) : 1]; + } + return $nodes; + } + + /** + * 获取节点列表 + * @param string $dirPath 路径 + * @param array $nodes 额外数据 + * @return array + */ + public static function getNodeTree($dirPath, $nodes = []) + { + foreach (self::scanDirFile($dirPath) as $filename) { + $matches = []; + if (!preg_match('|/(\w+)/controller/(\w+)|', str_replace(DIRECTORY_SEPARATOR, '/', $filename), $matches) || count($matches) !== 3) { + continue; + } + $className = env('app_namespace') . str_replace('/', '\\', $matches[0]); + if (!class_exists($className)) { + continue; + } + foreach (get_class_methods($className) as $funcName) { + if (strpos($funcName, '_') !== 0 && $funcName !== 'initialize') { + $nodes[] = self::parseNodeStr("{$matches[1]}/{$matches[2]}") . '/' . strtolower($funcName); + } + } + } + return $nodes; + } + + /** + * 驼峰转下划线规则 + * @param string $node + * @return string + */ + public static function parseNodeStr($node) + { + $tmp = []; + foreach (explode('/', $node) as $name) { + $tmp[] = strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + return trim(join('/', $tmp), '/'); + } + + /** + * 获取所有PHP文件 + * @param string $dirPath 目录 + * @param array $data 额外数据 + * @param string $ext 有文件后缀 + * @return array + */ + private static function scanDirFile($dirPath, $data = [], $ext = 'php') + { + foreach (scandir($dirPath) as $dir) { + if (strpos($dir, '.') === 0) { + continue; + } + $tmpPath = realpath($dirPath . DIRECTORY_SEPARATOR . $dir); + if (is_dir($tmpPath)) { + $data = array_merge($data, self::scanDirFile($tmpPath)); + } elseif (pathinfo($tmpPath, 4) === $ext) { + $data[] = $tmpPath; + } + } + return $data; + } + +} diff --git a/extend/service/RandomService.php b/extend/service/RandomService.php new file mode 100644 index 0000000..985a693 --- /dev/null +++ b/extend/service/RandomService.php @@ -0,0 +1,181 @@ +20, 'p2'=>30, 'p3'=>50); + * @param array $num 默认为1,即随机出来的数量 + * @param array $unique 默认为true,即当num>1时,随机出的数量是否唯一 + * @return mixed 当num为1时返回键名,反之返回一维数组 + */ + public static function lottery($ps, $num = 1, $unique = true) + { + if (!$ps) { + return $num == 1 ? '' : []; + } + if ($num >= count($ps) && $unique) { + $res = array_keys($ps); + return $num == 1 ? $res[0] : $res; + } + $max_exp = 0; + $res = []; + foreach ($ps as $key => $value) { + $value = substr($value, 0, stripos($value, ".") + 6); + $exp = strlen(strchr($value, '.')) - 1; + if ($exp > $max_exp) { + $max_exp = $exp; + } + } + $pow_exp = pow(10, $max_exp); + if ($pow_exp > 1) { + reset($ps); + foreach ($ps as $key => $value) { + $ps[$key] = $value * $pow_exp; + } + } + $pro_sum = array_sum($ps); + if ($pro_sum < 1) { + return $num == 1 ? '' : []; + } + for ($i = 0; $i < $num; $i++) { + $rand_num = mt_rand(1, $pro_sum); + reset($ps); + foreach ($ps as $key => $value) { + if ($rand_num <= $value) { + break; + } else { + $rand_num -= $value; + } + } + if ($num == 1) { + $res = $key; + break; + } else { + $res[$i] = $key; + } + if ($unique) { + $pro_sum -= $value; + unset($ps[$key]); + } + } + return $res; + } + + /** + * 获取全球唯一标识 + * @return string + */ + public static function uuid() + { + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + ); + } +} diff --git a/extend/service/SessionService.php b/extend/service/SessionService.php new file mode 100644 index 0000000..5987d8d --- /dev/null +++ b/extend/service/SessionService.php @@ -0,0 +1,53 @@ +soap = new \SoapClient($wsdl, $params); + } + + /** + * @param string $name SOAP调用方法名 + * @param array|string $arguments SOAP调用参数 + * @return array|string|bool + * @throws \think\Exception + */ + public function __call($name, $arguments) + { + try { + return $this->soap->__soapCall($name, $arguments); + } catch (\Exception $e) { + Log::error("Soap Error. Call {$name} Method --- " . $e->getMessage()); + throw new Exception($e->getMessage(), $e->getCode()); + } + } + +} diff --git a/extend/service/ToolsService.php b/extend/service/ToolsService.php new file mode 100644 index 0000000..f6bf602 --- /dev/null +++ b/extend/service/ToolsService.php @@ -0,0 +1,250 @@ +header('Token', ''); + empty($token) && $token = request()->post('token', ''); + empty($token) && $token = request()->get('token', ''); + list($name, $value) = explode('=', decode($token) . '='); +// var_dump(request()->header('token'));die; + if (!empty($value) && session_name() === $name) { + session_id($value); + } + if (request()->isOptions()) { +// header('Access-Control-Allow-Origin:http://127.0.0.1:8045'); +// header('Access-Control-Allow-Credentials:true'); +// header('Access-Control-Allow-Methods:GET,POST,OPTIONS'); +// header("Access-Control-Allow-Headers:*"); +// header('Content-Type:text/plain charset=UTF-8'); +// header('Access-Control-Max-Age:1728000'); +// header('HTTP/1.0 204 No Content'); +// header('Content-Length:0'); +// header('status:204'); + exit; + } + } + + /** + * Cors Request Header信息 + * @return array + */ + public static function corsRequestHander() + { + return [ +// 'Access-Control-Allow-Origin' => request()->header('origin', '*'), +// 'Access-Control-Allow-Methods' => 'GET,POST,OPTIONS', +// 'Access-Control-Allow-Credentials' => "true", + ]; + } + + /** + * 返回成功的操作 + * @param mixed $msg 消息内容 + * @param array $data 返回数据 + * @param integer $code 返回代码 + */ + public static function success($msg, $data = [], $code = 1) + { + $result = ['code' => $code, 'msg' => $msg, 'data' => $data, 'token' => encode(session_name() . '=' . session_id())]; + throw new HttpResponseException(Response::create($result, 'json', 200, self::corsRequestHander())); + } + + /** + * 返回失败的请求 + * @param mixed $msg 消息内容 + * @param array $data 返回数据 + * @param integer $code 返回代码 + */ + public static function error($msg, $data = [], $code = 0) + { + $result = ['code' => $code, 'msg' => $msg, 'data' => $data, 'token' => encode(session_name() . '=' . session_id())]; + throw new HttpResponseException(Response::create($result, 'json', $code, self::corsRequestHander())); + } + + /** + * Emoji原形转换为String + * @param string $content + * @return string + */ + public static function emojiEncode($content) + { + return json_decode(preg_replace_callback("/(\\\u[ed][0-9a-f]{3})/i", function ($str) { + return addslashes($str[0]); + }, json_encode($content))); + } + + /** + * Emoji字符串转换为原形 + * @param string $content + * @return string + */ + public static function emojiDecode($content) + { + return json_decode(preg_replace_callback('/\\\\\\\\/i', function () { + return '\\'; + }, json_encode($content))); + } + + /** + * 一维数据数组生成数据树 + * @param array $list 数据列表 + * @param string $id 父ID Key + * @param string $pid ID Key + * @param string $son 定义子数据Key + * @return array + */ + public static function arr2tree($list, $id = 'id', $pid = 'pid', $son = 'children') + { + list($tree, $map) = [[], []]; + foreach ($list as $item) { + $map[$item[$id]] = $item; + } + foreach ($list as $item) { + if (isset($item[$pid]) && isset($map[$item[$pid]])) { + $map[$item[$pid]][$son][] = &$map[$item[$id]]; + } else { + $tree[] = &$map[$item[$id]]; + } + } + unset($map); + return $tree; + } + + /** + * 一维数据数组生成数据树 + * @param array $list 数据列表 + * @param string $id ID Key + * @param string $pid 父ID Key + * @param string $path + * @param string $ppath + * @return array + */ + public static function arr2table(array $list, $id = 'id', $pid = 'pid', $path = 'path', $ppath = '') + { + $tree = []; + foreach (self::arr2tree($list, $id, $pid) as $attr) { + $attr[$path] = "{$ppath}-{$attr[$id]}"; + $attr['sub'] = isset($attr['sub']) ? $attr['sub'] : []; + $attr['spt'] = substr_count($ppath, '-'); + $attr['spl'] = str_repeat("   ├  ", $attr['spt']); +// $attr['key'] = (string)$attr['id']; +// $attr['value'] = (string)$attr['id']; +// $attr['title'] = $attr['node']; + $sub = $attr['sub']; + unset($attr['sub']); + $tree[] = $attr; + if (!empty($sub)) { + $tree = array_merge($tree, self::arr2table($sub, $id, $pid, $path, $attr[$path])); + } + } + return $tree; + } + + /** + * 获取数据树子ID + * @param array $list 数据列表 + * @param int $id 起始ID + * @param string $key 子Key + * @param string $pkey 父Key + * @return array + */ + public static function getArrSubIds($list, $id = 0, $key = 'id', $pkey = 'pid') + { + $ids = [intval($id)]; + foreach ($list as $vo) { + if (intval($vo[$pkey]) > 0 && intval($vo[$pkey]) === intval($id)) { + $ids = array_merge($ids, self::getArrSubIds($list, intval($vo[$key]), $key, $pkey)); + } + } + return $ids; + } + + /** + * 获取数据树子Code + * @param array $list 数据列表 + * @param string $code + * @param string $key 子Key + * @param string $pkey 父Key + * @return array + */ + public static function getArrSubCodes($list, $code = '', $key = 'code', $pkey = 'pcode') + { + $codes = [$code]; + foreach ($list as $vo) { + if ($vo[$pkey] && $vo[$pkey] === $code) { + $codes = array_merge($codes, self::getArrSubCodes($list, $vo[$pkey], $key, $pkey)); + } + } + return $codes; + } + + /** + * 写入CSV文件头部 + * @param string $filename 导出文件 + * @param array $headers CSV 头部(一级数组) + */ + public static function setCsvHeader($filename, array $headers) + { + header('Content-Type: application/octet-stream'); + header("Content-Disposition: attachment; filename=" . iconv('utf-8', 'gbk//TRANSLIT', $filename)); + echo @iconv('utf-8', 'gbk//TRANSLIT', "\"" . implode('","', $headers) . "\"\n"); + } + + /** + * 写入CSV文件内容 + * @param array $list 数据列表(二维数组或多维数组) + * @param array $rules 数据规则(一维数组) + */ + public static function setCsvBody(array $list, array $rules) + { + foreach ($list as $data) { + $rows = []; + foreach ($rules as $rule) { + $item = self::parseKeyDot($data, $rule); + $rows[] = $item === $data ? '' : $item; + } + echo @iconv('utf-8', 'gbk//TRANSLIT', "\"" . implode('","', $rows) . "\"\n"); + flush(); + } + } + + /** + * 根据数组key查询(可带点规则) + * @param array $data 数据 + * @param string $rule 规则,如: order.order_no + * @return mixed + */ + private static function parseKeyDot(array $data, $rule) + { + list($temp, $attr) = [$data, explode('.', trim($rule, '.'))]; + while ($key = array_shift($attr)) { + $temp = isset($temp[$key]) ? $temp[$key] : $temp; + } + return (is_string($temp) || is_numeric($temp)) ? str_replace('"', '""', "\t{$temp}") : ''; + } + +} diff --git a/extend/sms/Sms.php b/extend/sms/Sms.php new file mode 100644 index 0000000..b5cdec0 --- /dev/null +++ b/extend/sms/Sms.php @@ -0,0 +1,61 @@ + 'log/sms/order']); + } + + /** + * 发送单条短信 + * @param $to + * @param $message + * @param array $gateways + * @return array|bool + */ + public function vSend($to, $message, array $gateways = []) + { + $result = false; + try { + $result = $this->send($to, $message, $gateways); + } catch (InvalidArgumentException $e) { + Log::write(json_encode($e->getResults()), "sms-exception"); + } catch (NoGatewayAvailableException $e) { + Log::write(json_encode($e->getResults()), "sms-exception"); + } + Log::write(json_encode($result), "sms"); + return $result; + } + + /** + * 暂时保留 + * @param $phoneNumber + * @param $content + * @param string $vars + * @return mixed + */ + public function sends($phoneNumber, $content, $vars = '') + { + + $sms = new SubmailSms(); + $result = $sms->send($phoneNumber, $content); +// $result = $sms->multiSend($phoneNumber, $content, $vars); + Log::write(json_encode($result), "sms"); + return $result; + } +} diff --git a/extend/sms/SubmailSms.php b/extend/sms/SubmailSms.php new file mode 100644 index 0000000..e7b4b29 --- /dev/null +++ b/extend/sms/SubmailSms.php @@ -0,0 +1,55 @@ +messageSend = new MESSAGEsend($message_configs); + @$this->messageMultiSend = new MESSAGEMultiSend($message_configs); + + } + + public function send($phoneNumber, $content) + { + $this->messageSend->SetTo($phoneNumber); + $this->messageSend->SetContent("{$this->signName}{$content}"); + return $this->messageSend->send(); + } + + public function multiSend($phoneNumbers, $content, $vars) + { + if ($phoneNumbers) { + foreach ($phoneNumbers as $phoneNumber) { + $multi = new Multi(); + $multi->setTo($phoneNumber); + foreach ($vars as $key => $var) { + $multi->addVar($key, $var); + } + $this->messageMultiSend->addMulti($multi->build()); + } + } + $this->messageMultiSend->SetContent("{$this->signName}{$content}"); + return $this->messageMultiSend->multisend(); + } +} diff --git a/extend/sms/submail/SUBMAILAutoload.php b/extend/sms/submail/SUBMAILAutoload.php new file mode 100644 index 0000000..a31e910 --- /dev/null +++ b/extend/sms/submail/SUBMAILAutoload.php @@ -0,0 +1,30 @@ +=')) { + if (version_compare(PHP_VERSION, '5.3.0', '>=')) { + spl_autoload_register('SUBMAILAutoload', true, true); + } else { + spl_autoload_register('SUBMAILAutoload'); + } + } else { + //function __autoload($classname){ + function spl_autoload_register($classname){ + SUBMAILAutoload($classname); + } + } diff --git a/extend/sms/submail/app_config.php b/extend/sms/submail/app_config.php new file mode 100644 index 0000000..c3c126c --- /dev/null +++ b/extend/sms/submail/app_config.php @@ -0,0 +1,298 @@ +configs=$configs; + } + + public function setAddress($address,$name=''){ + $this->Address=$name.'<'.$address.'>'; + } + + public function setAddressbook($target){ + $this->Target=$target; + } + + protected function buildRequest(){ + $request=array(); + $request['address']=$this->Address; + if($this->Target!=''){ + $request['target']=$this->Target; + } + return $request; + } + + public function subscribe(){ + $addressbook=new mail($this->configs); + return $addressbook->subscribe($this->buildRequest()); + } + public function unsubscribe(){ + $addressbook=new mail($this->configs); + return $addressbook->unsubscribe($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/addressbookmessage.php b/extend/sms/submail/lib/addressbookmessage.php new file mode 100644 index 0000000..5bc8432 --- /dev/null +++ b/extend/sms/submail/lib/addressbookmessage.php @@ -0,0 +1,40 @@ +configs=$configs; + } + + public function setAddress($address){ + $this->Address=$address; + } + + public function setAddressbook($target){ + $this->Target=$target; + } + + protected function buildRequest(){ + $request=array(); + $request['address']=$this->Address; + if($this->Target!=''){ + $request['target']=$this->Target; + } + return $request; + + } + public function subscribe(){ + $addressbook=new message($this->configs); + return $addressbook->subscribe($this->buildRequest()); + } + public function unsubscribe(){ + $addressbook=new message($this->configs); + return $addressbook->unsubscribe($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/internationalsmsmultixsend.php b/extend/sms/submail/lib/internationalsmsmultixsend.php new file mode 100644 index 0000000..70fff09 --- /dev/null +++ b/extend/sms/submail/lib/internationalsmsmultixsend.php @@ -0,0 +1,39 @@ +configs=$configs; + } + + public function AddMulti($multi){ + array_push($this->Multi,$multi); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function buildRequest(){ + $request=array(); + $request['project']=$this->Project; + if(!empty($this->Multi)){ + $request['multi']=json_encode($this->Multi); + } + return $request; + } + + public function multixsend(){ + $intersms=new intersms($this->configs); + return $intersms->multixsend($this->buildRequest()); + } + } \ No newline at end of file diff --git a/extend/sms/submail/lib/internationalsmssend.php b/extend/sms/submail/lib/internationalsmssend.php new file mode 100644 index 0000000..aa52a9d --- /dev/null +++ b/extend/sms/submail/lib/internationalsmssend.php @@ -0,0 +1,36 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + public function SetContent($content){ + $this->Content=$content; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + $request['content']=$this->Content; + return $request; + } + public function send(){ + $intersms=new intersms($this->configs); + return $intersms->send($this->buildRequest()); + } + } \ No newline at end of file diff --git a/extend/sms/submail/lib/internationalsmsxsend.php b/extend/sms/submail/lib/internationalsmsxsend.php new file mode 100644 index 0000000..813fdfe --- /dev/null +++ b/extend/sms/submail/lib/internationalsmsxsend.php @@ -0,0 +1,56 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + + $request['project']=$this->Project; + if(!empty($this->Vars)){ + $request['vars']=json_encode($this->Vars); + } + return $request; + } + public function xsend(){ + $intersms=new intersms($this->configs); + return $intersms->xsend($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/intersms.php b/extend/sms/submail/lib/intersms.php new file mode 100644 index 0000000..04be554 --- /dev/null +++ b/extend/sms/submail/lib/intersms.php @@ -0,0 +1,147 @@ +intersms_configs=$intersms_configs; + if(!empty($intersms_configs['server'])){ + $this->base_url=$intersms_configs['server']; + } + } + + protected function createSignature($request){ + $r=""; + switch($this->signType){ + case 'normal': + $r=$this->intersms_configs['appkey']; + break; + case 'md5': + $r=$this->buildSignature($this->argSort($request)); + break; + case 'sha1': + $r=$this->buildSignature($this->argSort($request)); + break; + } + return $r; + } + + protected function buildSignature($request){ + $arg=""; + $app=$this->intersms_configs['appid']; + $appkey=$this->intersms_configs['appkey']; + while (list ($key, $val) = each ($request)) { + $arg.=$key."=".$val."&"; + } + $arg = substr($arg,0,count($arg)-2); + if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} + + if($this->signType=='sha1'){ + $r=sha1($app.$appkey.$arg.$app.$appkey); + }else{ + $r=md5($app.$appkey.$arg.$app.$appkey); + } + return $r; + } + + protected function argSort($request) { + ksort($request); + reset($request); + return $request; + } + + protected function getTimestamp(){ + $api=$this->base_url.'service/timestamp.json'; + $ch = curl_init($api) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true) ; + $output = curl_exec($ch) ; + $timestamp=json_decode($output,true); + + return $timestamp['timestamp']; + } + + protected function APIHttpRequestCURL($api,$post_data,$method='post'){ + if($method!='get'){ + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $api, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => http_build_query($post_data), + CURLOPT_CUSTOMREQUEST => strtoupper($method), + CURLOPT_HTTPHEADER => array("Content-Type: application/x-www-form-urlencoded") + )); + }else{ + $url=$api.'?'.http_build_query($post_data); + $ch = curl_init($url) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1) ; + } + $output = curl_exec($ch); + curl_close($ch); + $output = trim($output, "\xEF\xBB\xBF"); + return json_decode($output,true); + } + + public function send($request){ + $api=$this->base_url.'internationalsms/send.json'; + $request['appid']=$this->intersms_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->intersms_configs['sign_type']) + || $this->intersms_configs['sign_type']=="" + || $this->intersms_configs['sign_type']!="normal" + || $this->intersms_configs['sign_type']!="md5" + || $this->intersms_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->intersms_configs['sign_type']; + $request['sign_type']=$this->intersms_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + + public function xsend($request){ + $api=$this->base_url.'internationalsms/xsend.json'; + $request['appid']=$this->intersms_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->intersms_configs['sign_type']) + || $this->intersms_configs['sign_type']=="" + || $this->intersms_configs['sign_type']!="normal" + || $this->intersms_configs['sign_type']!="md5" + || $this->intersms_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->intersms_configs['sign_type']; + $request['sign_type']=$this->intersms_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + public function multixsend($request){ + $api=$this->base_url.'internationalsms/multixsend.json'; + $request['appid']=$this->intersms_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->intersms_configs['sign_type']) + || $this->intersms_configs['sign_type']=="" + || $this->intersms_configs['sign_type']!="normal" + || $this->intersms_configs['sign_type']!="md5" + || $this->intersms_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->intersms_configs['sign_type']; + $request['sign_type']=$this->intersms_configs['sign_type']; + } + + + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + } diff --git a/extend/sms/submail/lib/mail.php b/extend/sms/submail/lib/mail.php new file mode 100644 index 0000000..f43177d --- /dev/null +++ b/extend/sms/submail/lib/mail.php @@ -0,0 +1,168 @@ +mail_configs=$mail_config; + if(!empty($mail_config['server'])){ + $this->base_url=$mail_config['server']; + } + echo $this->base_url; + } + + protected function createSignature($request){ + $r=""; + switch($this->signType){ + case 'normal': + $r=$this->mail_configs['appkey']; + break; + case 'md5': + $r=$this->buildSignature($this->argSort($request)); + break; + case 'sha1': + $r=$this->buildSignature($this->argSort($request)); + break; + } + return $r; + } + + protected function buildSignature($request){ + $arg=""; + $app=$this->mail_configs['appid']; + $appkey=$this->mail_configs['appkey']; + while (list ($key, $val) = each ($request)) { + if (strpos($key,"attachments")===false){ + $arg.=$key."=".$val."&"; + } + } + $arg = substr($arg,0,count($arg)-2); + if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} + if($this->signType=='sha1'){ + $r=sha1($app.$appkey.$arg.$app.$appkey); + }else{ + $r=md5($app.$appkey.$arg.$app.$appkey); + } + return $r; + } + + protected function argSort($request) { + ksort($request); + reset($request); + return $request; + } + + public function getTimestamp(){ + $api=$this->base_url.'service/timestamp.json'; + $ch = curl_init($api) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true) ; + $output = curl_exec($ch) ; + $timestamp=json_decode($output,true); + + return $timestamp['timestamp']; + } + + protected function APIHttpRequestCURL($api,$post_data,$method='post'){ + if($method!='get'){ + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $api, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => http_build_query($post_data), + CURLOPT_CUSTOMREQUEST => strtoupper($method), + CURLOPT_HTTPHEADER => array("Content-Type: application/x-www-form-urlencoded") + )); + }else{ + $url=$api.'?'.http_build_query($post_data); + $ch = curl_init($url) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1) ; + } + $output = curl_exec($ch); + curl_close($ch); + $output = trim($output, "\xEF\xBB\xBF"); + return json_decode($output,true); + } + + + public function send($request){ + $api=$this->base_url.'mail/send.json'; + $request['appid']=$this->mail_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mail_configs['sign_type']) + && $this->mail_configs['sign_type']=="" + && $this->mail_configs['sign_type']!="normal" + && $this->mail_configs['sign_type']!="md5" + && $this->mail_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mail_configs['sign_type']; + $request['sign_type']=$this->mail_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + + return $send; + } + + public function xsend($request){ + $api=$this->base_url.'mail/xsend.json'; + $request['appid']=$this->mail_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mail_configs['sign_type']) + && $this->mail_configs['sign_type']=="" + && $this->mail_configs['sign_type']!="normal" + && $this->mail_configs['sign_type']!="md5" + && $this->mail_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mail_configs['sign_type']; + $request['sign_type']=$this->mail_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $xsend=$this->APIHttpRequestCURL($api,$request); + return $xsend; + } + + public function subscribe($request){ + $api=$this->base_url.'addressbook/mail/subscribe.json'; + $request['appid']=$this->mail_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mail_configs['sign_type']) + && $this->mail_configs['sign_type']=="" + && $this->mail_configs['sign_type']!="normal" + && $this->mail_configs['sign_type']!="md5" + && $this->mail_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mail_configs['sign_type']; + $request['sign_type']=$this->mail_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $subscribe=$this->APIHttpRequestCURL($api,$request); + return $subscribe; + } + + public function unsubscribe($request){ + $api=$this->base_url.'addressbook/mail/unsubscribe.json'; + $request['appid']=$this->mail_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mail_configs['sign_type']) + && $this->mail_configs['sign_type']=="" + && $this->mail_configs['sign_type']!="normal" + && $this->mail_configs['sign_type']!="md5" + && $this->mail_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mail_configs['sign_type']; + $request['sign_type']=$this->mail_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $unsubscribe=$this->APIHttpRequestCURL($api,$request); + return $unsubscribe; + } + } diff --git a/extend/sms/submail/lib/mailsend.php b/extend/sms/submail/lib/mailsend.php new file mode 100644 index 0000000..02b3342 --- /dev/null +++ b/extend/sms/submail/lib/mailsend.php @@ -0,0 +1,179 @@ +configs=$configs; + } + + public function AddTo($address,$name=''){ + array_push($this->To,array('address'=>$address,'name'=>$name)); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetSender($sender,$name=''){ + $this->From=$sender; + $this->From_name=$name; + } + + public function SetReply($reply){ + $this->Reply=$reply; + } + + public function AddCc($address,$name=''){ + array_push($this->Cc,array('address'=>$address,'name'=>$name)); + } + + public function AddBcc($address,$name=''){ + array_push($this->Bcc,array('address'=>$address,'name'=>$name)); + } + + public function SetSubject($subject){ + $this->Subject=$subject; + } + + public function SetText($text){ + $this->Text=$text; + } + + public function SetHtml($html){ + $this->Html=$html; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function AddLink($key,$val){ + $this->Links[$key]=$val; + } + + public function AddAttachment($attachment){ + array_push($this->Attachments,$attachment); + } + + public function AddHeaders($key,$val){ + $this->Headers[$key]=$val; + } + + public function setAsynchronous($asynchronous){ + if($asynchronous==true){ + $this->asynchronous=true; + }else{ + $this->asynchronous=false; + } + } + + protected function buildRequest(){ + $request=array(); + if(!empty($this->To)){ + $request['to']=''; + foreach($this->To as $tmp){ + $request['to'].=$tmp['name'].'<'.$tmp['address'].'>,'; + } + $request['to'] = substr($request['to'],0,count($request['to'])-2); + } + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + $request['from']=$this->From; + if($this->From_name!=''){ + $request['from_name']=$this->From_name; + } + if($this->Reply!=''){ + $request['reply']=$this->Reply; + } + if(!empty($this->Cc)){ + $request['cc']=''; + foreach($this->Cc as $tmp){ + $request['cc'].=$tmp['name'].'<'.$tmp['address'].'>,'; + } + $request['cc'] = substr($request['cc'],0,count($request['cc'])-2); + } + if(!empty($this->Bcc)){ + $request['bcc']=''; + foreach($this->Bcc as $tmp){ + $request['bcc'].=$tmp['name'].'<'.$tmp['address'].'>,'; + } + $request['bcc'] = substr($request['bcc'],0,count($request['bcc'])-2); + } + $request['subject']=$this->Subject; + if($this->Text!=''){ + $request['text']=$this->Text; + } + + if($this->Html!=''){ + $request['html']=$this->Html; + } + + if(!empty($this->Vars)){ + $request['vars']=json_encode($this->Vars); + } + + if(!empty($this->Links)){ + $request['links']=json_encode($this->Links); + } + + if(!empty($this->Attachments)){ + for($i=0;$iAttachments);$i++){ + //$request['attachments['.$i.']']="@".$this->Attachments[$i]; + $request['attachments['.$i.']'] = curl_file_create($this->Attachments[$i]); + } + } + + if(!empty($this->asynchronous)){ + $request['asynchronous']= $this->asynchronous; + } + + if(!empty($this->Headers)){ + $request['headers']=json_encode($this->Headers); + } + + return $request; + } + + public function send(){ + $mail=new mail($this->configs); + return $mail->send($this->buildRequest()); + } + } diff --git a/extend/sms/submail/lib/mailxsend.php b/extend/sms/submail/lib/mailxsend.php new file mode 100644 index 0000000..1754184 --- /dev/null +++ b/extend/sms/submail/lib/mailxsend.php @@ -0,0 +1,166 @@ +configs=$configs; + } + + public function AddTo($address,$name=''){ + array_push($this->To,array('address'=>$address,'name'=>$name)); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetSender($sender,$name=''){ + $this->From=$sender; + $this->From_name=$name; + } + + public function SetReply($reply){ + $this->Reply=$reply; + } + + public function AddCc($address,$name=''){ + array_push($this->Cc,array('address'=>$address,'name'=>$name)); + } + + public function AddBcc($address,$name=''){ + array_push($this->Bcc,array('address'=>$address,'name'=>$name)); + } + + public function SetSubject($subject){ + $this->Subject=$subject; + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function AddLink($key,$val){ + $this->Links[$key]=$val; + } + + public function AddHeaders($key,$val){ + $this->Headers[$key]=$val; + } + + public function setAsynchronous($asynchronous){ + if($asynchronous==true){ + $this->asynchronous="true"; + }else{ + $this->asynchronous="false"; + } + } + + protected function buildRequest(){ + $request=array(); + + if(!empty($this->To)){ + $request['to']=''; + foreach($this->To as $tmp){ + $request['to'].=$tmp['name'].'<'.$tmp['address'].'>,'; + } + $request['to'] = substr($request['to'],0,count($request['to'])-2); + } + + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + + if($this->From!=''){ + $request['from']=$this->From; + } + + if($this->From_name!=''){ + $request['from_name']=$this->From_name; + } + + + if($this->Reply!=''){ + $request['reply']=$this->Reply; + } + + if(!empty($this->Cc)){ + $request['cc']=''; + foreach($this->Cc as $tmp){ + $request['cc'].=$tmp['name'].'<'.$tmp['address'].'>,'; + } + $request['cc'] = substr($request['cc'],0,count($request['cc'])-2); + } + + if(!empty($this->Bcc)){ + $request['bcc']=''; + foreach($this->Bcc as $tmp){ + $request['bcc'].=$tmp['name'].'<'.$tmp['address'].'>,'; + } + $request['bcc'] = substr($request['bcc'],0,count($request['bcc'])-2); + } + + if($this->Subject!=''){ + $request['subject']=$this->Subject; + } + + $request['project']=$this->Project; + + if(!empty($this->Vars)){ + $request['vars']=json_encode($this->Vars); + } + + if(!empty($this->Links)){ + $request['links']=json_encode($this->Links); + } + + if(!empty($this->asynchronous)){ + $request['asynchronous']= $this->asynchronous; + } + + if(!empty($this->Headers)){ + $request['headers']=json_encode($this->Headers); + } + return $request; + + } + + public function xsend(){ + $mail=new mail($this->configs); + return $mail->xsend($this->buildRequest()); + } + } \ No newline at end of file diff --git a/extend/sms/submail/lib/message.php b/extend/sms/submail/lib/message.php new file mode 100644 index 0000000..bffd909 --- /dev/null +++ b/extend/sms/submail/lib/message.php @@ -0,0 +1,283 @@ +message_configs=$message_configs; + if(!empty($message_configs['server'])){ + $this->base_url=$message_configs['server']; + } + } + + protected function createSignature($request){ + $r=""; + switch($this->signType){ + case 'normal': + $r=$this->message_configs['appkey']; + break; + case 'md5': + $r=$this->buildSignature($this->argSort($request)); + break; + case 'sha1': + $r=$this->buildSignature($this->argSort($request)); + break; + } + return $r; + } + + protected function buildSignature($request){ + $arg=""; + $app=$this->message_configs['appid']; + $appkey=$this->message_configs['appkey']; + while (list ($key, $val) = each ($request)) { + $arg.=$key."=".$val."&"; + } + $arg = substr($arg,0,count($arg)-2); + if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} + + if($this->signType=='sha1'){ + $r=sha1($app.$appkey.$arg.$app.$appkey); + }else{ + $r=md5($app.$appkey.$arg.$app.$appkey); + } + return $r; + } + + protected function argSort($request) { + ksort($request); + reset($request); + return $request; + } + + protected function getTimestamp(){ + $api=$this->base_url.'service/timestamp.json'; + $ch = curl_init($api) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true) ; + $output = curl_exec($ch) ; + $timestamp=json_decode($output,true); + + return $timestamp['timestamp']; + } + + protected function APIHttpRequestCURL($api,$post_data,$method='post'){ + if($method!='get'){ + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $api, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => http_build_query($post_data), + CURLOPT_CUSTOMREQUEST => strtoupper($method), + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_HTTPHEADER => array("Content-Type: application/x-www-form-urlencoded") + )); + }else{ + $url=$api.'?'.http_build_query($post_data); + $ch = curl_init($url) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1) ; + curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,false); + curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,false); + } + $output = curl_exec($ch); + curl_close($ch); + $output = trim($output, "\xEF\xBB\xBF"); + return json_decode($output,true); + } + + public function send($request){ + $api=$this->base_url.'message/send.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + + public function xsend($request){ + $api=$this->base_url.'message/xsend.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + public function multixsend($request){ + $api=$this->base_url.'message/multixsend.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + + public function multisend($request){ + $api=$this->base_url.'message/multisend.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + + public function subscribe($request){ + $api=$this->base_url.'addressbook/message/subscribe.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + + public function unsubscribe($request){ + $api=$this->base_url.'addressbook/message/unsubscribe.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + public function log($request){ + $api=$this->base_url.'log/message.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request); + } + public function getTemplate($request){ + $api=$this->base_url.'message/template.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request,'get'); + } + public function postTemplate($request){ + $api=$this->base_url.'message/template.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request,'post'); + } + public function putTemplate($request){ + $api=$this->base_url.'message/template.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request,'PUT'); + } + public function deleteTemplate($request){ + $api=$this->base_url.'message/template.json'; + $request['appid']=$this->message_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->message_configs['sign_type']) + && $this->message_configs['sign_type']=="" + && $this->message_configs['sign_type']!="normal" + && $this->message_configs['sign_type']!="md5" + && $this->message_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->message_configs['sign_type']; + $request['sign_type']=$this->message_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + return $this->APIHttpRequestCURL($api,$request,'DELETE'); + } + } diff --git a/extend/sms/submail/lib/messagelog.php b/extend/sms/submail/lib/messagelog.php new file mode 100644 index 0000000..1775a76 --- /dev/null +++ b/extend/sms/submail/lib/messagelog.php @@ -0,0 +1,113 @@ +configs=$configs; + } + + public function setRecipient($recipient){ + $this->recipient=$recipient; + } + + public function setProject($project){ + $this->project=$project; + } + + public function setResultStatus($result_status){ + if($result_status=='delivered' || $result_status=='dropped'){ + $this->result_status=$result_status; + } + } + + public function setStartDate($start_date){ + if($start_date!=''){ + $this->start_date=strtotime($start_date); + } + } + + public function setEndDate($end_date){ + if($end_date!=''){ + $this->end_date=strtotime($end_date); + } + } + + public function setOrderBy($order_by){ + if($order_by=='asc' || $order_by=='desc'){ + $this->order_by=$order_by; + } + } + + public function setRows($rows){ + if($rows>=10 && $rows<=1000){ + $this->rows=$rows; + } + } + + public function setOffset($offset){ + if($offset>=0){ + $this->offset=$offset; + } + } + + public function buildRequest(){ + $request=array(); + + if($this->recipient!=''){ + $request['recipient']=$this->recipient; + } + + if($this->project!=''){ + $request['project']=$this->project; + } + + if($this->result_status!=''){ + $request['result_status']=$this->result_status; + } + + if($this->start_date!=''){ + $request['start_date']=$this->start_date; + } + + if($this->end_date!=''){ + $request['end_date']=$this->end_date; + } + + if($this->order_by!=''){ + $request['order_by']=$this->order_by; + } + + if($this->rows!=''){ + $request['rows']=$this->rows; + } + + if($this->offset!=''){ + $request['offset']=$this->offset; + } + + return $request; + } + + public function log(){ + $message=new message($this->configs); + return $message->log($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagemultisend.php b/extend/sms/submail/lib/messagemultisend.php new file mode 100644 index 0000000..d5a2cc6 --- /dev/null +++ b/extend/sms/submail/lib/messagemultisend.php @@ -0,0 +1,35 @@ +configs=$configs; + } + + public function AddMulti($multi){ + array_push($this->Multi,$multi); + } + + public function SetContent($content){ + $this->Content=$content; + } + + public function buildRequest(){ + $request=array(); + $request['content']=$this->Content; + if(!empty($this->Multi)){ + $request['multi']=json_encode($this->Multi); + } + return $request; + } + + public function multisend(){ + $message=new message($this->configs); + return $message->multisend($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagemultixsend.php b/extend/sms/submail/lib/messagemultixsend.php new file mode 100644 index 0000000..d31a501 --- /dev/null +++ b/extend/sms/submail/lib/messagemultixsend.php @@ -0,0 +1,39 @@ +configs=$configs; + } + + public function AddMulti($multi){ + array_push($this->Multi,$multi); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function buildRequest(){ + $request=array(); + $request['project']=$this->Project; + if(!empty($this->Multi)){ + $request['multi']=json_encode($this->Multi); + } + return $request; + } + + public function multixsend(){ + $message=new message($this->configs); + return $message->multixsend($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagesend.php b/extend/sms/submail/lib/messagesend.php new file mode 100644 index 0000000..bb2c524 --- /dev/null +++ b/extend/sms/submail/lib/messagesend.php @@ -0,0 +1,39 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + + public function SetContent($content){ + $this->Content=$content; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + $request['content']=$this->Content; + return $request; + } + public function send(){ + $message=new message($this->configs); + return $message->send($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagetemplatedelete.php b/extend/sms/submail/lib/messagetemplatedelete.php new file mode 100644 index 0000000..c8d3b23 --- /dev/null +++ b/extend/sms/submail/lib/messagetemplatedelete.php @@ -0,0 +1,28 @@ +configs=$configs; + } + + public function SetTemplate($template_id){ + $this->template_id=trim($template_id); + } + + public function buildRequest(){ + $request=array(); + + $request['template_id']=$this->template_id; + + return $request; + } + public function deleteTemplate(){ + $message=new message($this->configs); + return $message->deleteTemplate($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagetemplateget.php b/extend/sms/submail/lib/messagetemplateget.php new file mode 100644 index 0000000..b5dcca4 --- /dev/null +++ b/extend/sms/submail/lib/messagetemplateget.php @@ -0,0 +1,28 @@ +configs=$configs; + } + + public function SetTemplate($template_id){ + $this->template_id=trim($template_id); + } + + public function buildRequest(){ + $request=array(); + if(!empty($this->template_id)){ + $request['template_id']=$this->template_id; + } + return $request; + } + public function getTemplate(){ + $message=new message($this->configs); + return $message->getTemplate($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagetemplatepost.php b/extend/sms/submail/lib/messagetemplatepost.php new file mode 100644 index 0000000..233e0c6 --- /dev/null +++ b/extend/sms/submail/lib/messagetemplatepost.php @@ -0,0 +1,45 @@ +configs=$configs; + } + + public function SetTitle($sms_title){ + $this->sms_title=trim($sms_title); + } + + public function SetSignature($sms_signature){ + $this->sms_signature=trim($sms_signature); + } + + public function SetContent($sms_content){ + $this->sms_content=trim($sms_content); + } + + public function buildRequest(){ + $request=array(); + if(!empty($this->sms_title)){ + $request['sms_title']=$this->sms_title; + } + + $request['sms_signature']=$this->sms_signature; + + $request['sms_content']=$this->sms_content; + + return $request; + } + public function postTemplate(){ + $message=new message($this->configs); + return $message->postTemplate($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagetemplateput.php b/extend/sms/submail/lib/messagetemplateput.php new file mode 100644 index 0000000..0c41bb9 --- /dev/null +++ b/extend/sms/submail/lib/messagetemplateput.php @@ -0,0 +1,55 @@ +configs=$configs; + } + + public function SetTemplate($template_id){ + $this->template_id=trim($template_id); + } + + public function SetTitle($sms_title){ + $this->sms_title=trim($sms_title); + } + + public function SetSignature($sms_signature){ + $this->sms_signature=trim($sms_signature); + } + + public function SetContent($sms_content){ + $this->sms_content=trim($sms_content); + } + + public function buildRequest(){ + $request=array(); + + $request['template_id']=$this->template_id; + + if(!empty($this->sms_title)){ + $request['sms_title']=$this->sms_title; + } + + $request['sms_signature']=$this->sms_signature; + + $request['sms_content']=$this->sms_content; + + return $request; + } + public function putTemplate(){ + $message=new message($this->configs); + return $message->putTemplate($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/messagexsend.php b/extend/sms/submail/lib/messagexsend.php new file mode 100644 index 0000000..162e4d4 --- /dev/null +++ b/extend/sms/submail/lib/messagexsend.php @@ -0,0 +1,55 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + + $request['project']=$this->Project; + if(!empty($this->Vars)){ + $request['vars']=json_encode($this->Vars); + } + return $request; + } + public function xsend(){ + $message=new message($this->configs); + return $message->xsend($this->buildRequest()); + } + + } diff --git a/extend/sms/submail/lib/mobiledata.php b/extend/sms/submail/lib/mobiledata.php new file mode 100644 index 0000000..9ebebf5 --- /dev/null +++ b/extend/sms/submail/lib/mobiledata.php @@ -0,0 +1,148 @@ +mobiledata_configs=$configs; + if(!empty($configs['server'])){ + $this->base_url=$configs['server']; + } + } + + protected function createSignature($request){ + $r=""; + switch($this->signType){ + case 'normal': + $r=$this->mobiledata_configs['appkey']; + break; + case 'md5': + $r=$this->buildSignature($this->argSort($request)); + break; + case 'sha1': + $r=$this->buildSignature($this->argSort($request)); + break; + } + return $r; + } + + protected function buildSignature($request){ + $arg=""; + $app=$this->mobiledata_configs['appid']; + $appkey=$this->mobiledata_configs['appkey']; + while (list ($key, $val) = each ($request)) { + $arg.=$key."=".$val."&"; + } + $arg = substr($arg,0,count($arg)-2); + if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} + + if($this->signType=='sha1'){ + $r=sha1($app.$appkey.$arg.$app.$appkey); + }else{ + $r=md5($app.$appkey.$arg.$app.$appkey); + } + + return $r; + } + + protected function argSort($request) { + ksort($request); + reset($request); + return $request; + } + + protected function getTimestamp(){ + $api=$this->base_url.'service/timestamp.json'; + $ch = curl_init($api) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true) ; + $output = curl_exec($ch) ; + $timestamp=json_decode($output,true); + + return $timestamp['timestamp']; + } + + protected function APIHttpRequestCURL($api,$post_data,$method='post'){ + if($method!='get'){ + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $api, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => http_build_query($post_data), + CURLOPT_CUSTOMREQUEST => strtoupper($method), + CURLOPT_HTTPHEADER => array("Content-Type: application/x-www-form-urlencoded") + )); + }else{ + $url=$api.'?'.http_build_query($post_data); + $ch = curl_init($url) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1) ; + } + $output = curl_exec($ch); + curl_close($ch); + $output = trim($output, "\xEF\xBB\xBF"); + return json_decode($output,true); + } + + public function package($request){ + $api=$this->base_url.'mobiledata/package.json'; + $request['appid']=$this->mobiledata_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mobiledata_configs['sign_type']) + && $this->mobiledata_configs['sign_type']=="" + && $this->mobiledata_configs['sign_type']!="normal" + && $this->mobiledata_configs['sign_type']!="md5" + && $this->mobiledata_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mobiledata_configs['sign_type']; + $request['sign_type']=$this->mobiledata_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $package=$this->APIHttpRequestCURL($api,$request); + return $package; + } + + public function TOService($request){ + $api=$this->base_url.'mobiledata/toservice.json'; + $request['appid']=$this->mobiledata_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mobiledata_configs['sign_type']) + && $this->mobiledata_configs['sign_type']=="" + && $this->mobiledata_configs['sign_type']!="normal" + && $this->mobiledata_configs['sign_type']!="md5" + && $this->mobiledata_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mobiledata_configs['sign_type']; + $request['sign_type']=$this->mobiledata_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $TOService=$this->APIHttpRequestCURL($api,$request); + return $TOService; + } + + public function charge($request){ + $api=$this->base_url.'mobiledata/charge.json'; + $request['appid']=$this->mobiledata_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->mobiledata_configs['sign_type']) + && $this->mobiledata_configs['sign_type']=="" + && $this->mobiledata_configs['sign_type']!="normal" + && $this->mobiledata_configs['sign_type']!="md5" + && $this->mobiledata_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->mobiledata_configs['sign_type']; + $request['sign_type']=$this->mobiledata_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $charge=$this->APIHttpRequestCURL($api,$request); + return $charge; + } + + } diff --git a/extend/sms/submail/lib/mobiledatacharge.php b/extend/sms/submail/lib/mobiledatacharge.php new file mode 100644 index 0000000..6fc6715 --- /dev/null +++ b/extend/sms/submail/lib/mobiledatacharge.php @@ -0,0 +1,73 @@ +configs=$configs; + } + + public function AddTo($address){ + array_push($this->To,trim($address)); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetCM($cm){ + $this->cm=$cm; + } + + public function SetCU($cu){ + $this->cu=$cu; + } + + public function SetCT($ct){ + $this->ct=$ct; + } + + public function buildRequest(){ + $request=array(); + if(!empty($this->To)){ + $request['to']=''; + foreach($this->To as $tmp){ + $request['to'].=$tmp.','; + } + $request['to'] = substr($request['to'],0,count($request['to'])-2); + } + if($this->cm !=''){ + $request['cm']=$this->cm; + } + if($this->cu !=''){ + $request['cu']=$this->cu; + } + if($this->ct !=''){ + $request['ct']=$this->ct; + } + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + return $request; + } + public function charge(){ + $mobiledata=new mobiledata($this->configs); + return $mobiledata->charge($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/mobiledatapackage.php b/extend/sms/submail/lib/mobiledatapackage.php new file mode 100644 index 0000000..b9da173 --- /dev/null +++ b/extend/sms/submail/lib/mobiledatapackage.php @@ -0,0 +1,19 @@ +configs=$configs; + } + + public function buildRequest(){ + $request=array(); + return $request; + } + public function package(){ + $mobiledata=new mobiledata($this->configs); + return $mobiledata->package($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/mobiledatatoservice.php b/extend/sms/submail/lib/mobiledatatoservice.php new file mode 100644 index 0000000..dce5e16 --- /dev/null +++ b/extend/sms/submail/lib/mobiledatatoservice.php @@ -0,0 +1,46 @@ +configs=$configs; + } + + public function AddTo($address){ + array_push($this->To,trim($address)); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function buildRequest(){ + $request=array(); + if(!empty($this->To)){ + $request['to']=''; + foreach($this->To as $tmp){ + $request['to'].=$tmp.','; + } + $request['to'] = substr($request['to'],0,count($request['to'])-2); + } + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + return $request; + } + public function TOService(){ + $mobiledata=new mobiledata($this->configs); + return $mobiledata->TOService($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/multi.php b/extend/sms/submail/lib/multi.php new file mode 100644 index 0000000..4dc2224 --- /dev/null +++ b/extend/sms/submail/lib/multi.php @@ -0,0 +1,21 @@ +To=$address; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function Build(){ + $multi=array("to"=>$this->To,"vars"=>$this->Vars); + unset($this->To); + unset($this->Vars); + return $multi; + } + } \ No newline at end of file diff --git a/extend/sms/submail/lib/voice.php b/extend/sms/submail/lib/voice.php new file mode 100644 index 0000000..7fbdf8d --- /dev/null +++ b/extend/sms/submail/lib/voice.php @@ -0,0 +1,163 @@ +voice_configs=$voice_configs; + if(!empty($voice_configs['server'])){ + $this->base_url=$voice_configs['server']; + } + } + + protected function createSignature($request){ + $r=""; + switch($this->signType){ + case 'normal': + $r=$this->voice_configs['appkey']; + break; + case 'md5': + $r=$this->buildSignature($this->argSort($request)); + break; + case 'sha1': + $r=$this->buildSignature($this->argSort($request)); + break; + } + return $r; + } + + protected function buildSignature($request){ + $arg=""; + $app=$this->voice_configs['appid']; + $appkey=$this->voice_configs['appkey']; + while (list ($key, $val) = each ($request)) { + $arg.=$key."=".$val."&"; + } + $arg = substr($arg,0,count($arg)-2); + if(get_magic_quotes_gpc()){$arg = stripslashes($arg);} + + if($this->signType=='sha1'){ + $r=sha1($app.$appkey.$arg.$app.$appkey); + }else{ + $r=md5($app.$appkey.$arg.$app.$appkey); + } + + return $r; + } + + protected function argSort($request) { + ksort($request); + reset($request); + return $request; + } + + protected function getTimestamp(){ + $api=$this->base_url.'service/timestamp.json'; + $ch = curl_init($api) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, true) ; + $output = curl_exec($ch) ; + $timestamp=json_decode($output,true); + + return $timestamp['timestamp']; + } + + protected function APIHttpRequestCURL($api,$post_data,$method='post'){ + if($method!='get'){ + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $api, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POSTFIELDS => http_build_query($post_data), + CURLOPT_CUSTOMREQUEST => strtoupper($method), + CURLOPT_HTTPHEADER => array("Content-Type: application/x-www-form-urlencoded") + )); + }else{ + $url=$api.'?'.http_build_query($post_data); + $ch = curl_init($url) ; + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1) ; + curl_setopt($ch, CURLOPT_BINARYTRANSFER, 1) ; + } + $output = curl_exec($ch); + curl_close($ch); + $output = trim($output, "\xEF\xBB\xBF"); + return json_decode($output,true); + } + + public function verify($request){ + $api=$this->base_url.'voice/verify.json'; + $request['appid']=$this->voice_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->voice_configs['sign_type']) + && $this->voice_configs['sign_type']=="" + && $this->voice_configs['sign_type']!="normal" + && $this->voice_configs['sign_type']!="md5" + && $this->voice_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->voice_configs['sign_type']; + $request['sign_type']=$this->voice_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + public function xsend($request){ + $api=$this->base_url.'voice/xsend.json'; + $request['appid']=$this->voice_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->voice_configs['sign_type']) + && $this->voice_configs['sign_type']=="" + && $this->voice_configs['sign_type']!="normal" + && $this->voice_configs['sign_type']!="md5" + && $this->voice_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->voice_configs['sign_type']; + $request['sign_type']=$this->voice_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + public function send($request){ + $api=$this->base_url.'voice/send.json'; + $request['appid']=$this->voice_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->voice_configs['sign_type']) + && $this->voice_configs['sign_type']=="" + && $this->voice_configs['sign_type']!="normal" + && $this->voice_configs['sign_type']!="md5" + && $this->voice_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->voice_configs['sign_type']; + $request['sign_type']=$this->voice_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + public function multixsend($request){ + $api=$this->base_url.'voice/multixsend.json'; + $request['appid']=$this->voice_configs['appid']; + $request['timestamp']=$this->getTimestamp(); + if(empty($this->voice_configs['sign_type']) + && $this->voice_configs['sign_type']=="" + && $this->voice_configs['sign_type']!="normal" + && $this->voice_configs['sign_type']!="md5" + && $this->voice_configs['sign_type']!="sha1"){ + $this->signType='normal'; + }else{ + $this->signType=$this->voice_configs['sign_type']; + $request['sign_type']=$this->voice_configs['sign_type']; + } + $request['signature']=$this->createSignature($request); + $send=$this->APIHttpRequestCURL($api,$request); + return $send; + } + } diff --git a/extend/sms/submail/lib/voicemultixsend.php b/extend/sms/submail/lib/voicemultixsend.php new file mode 100644 index 0000000..61cd4bb --- /dev/null +++ b/extend/sms/submail/lib/voicemultixsend.php @@ -0,0 +1,40 @@ +configs=$configs; + } + + public function AddMulti($multi){ + array_push($this->Multi,$multi); + } + + public function AddAddressbook($addressbook){ + array_push($this->Addressbook,$addressbook); + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function buildRequest(){ + $request=array(); + $request['project']=$this->Project; + if(!empty($this->Multi)){ + $request['multi']=json_encode($this->Multi); + } + return $request; + } + + public function multixsend(){ + $voice=new voice($this->configs); + return $voice->multixsend($this->buildRequest()); + } + + } diff --git a/extend/sms/submail/lib/voicesend.php b/extend/sms/submail/lib/voicesend.php new file mode 100644 index 0000000..d9f33a3 --- /dev/null +++ b/extend/sms/submail/lib/voicesend.php @@ -0,0 +1,37 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + + public function SetContent($content){ + $this->Content=$content; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + $request['content']=$this->Content; + return $request; + } + public function send(){ + $voice=new voice($this->configs); + return $voice->send($this->buildRequest()); + } + + } diff --git a/extend/sms/submail/lib/voiceverify.php b/extend/sms/submail/lib/voiceverify.php new file mode 100644 index 0000000..943c4f6 --- /dev/null +++ b/extend/sms/submail/lib/voiceverify.php @@ -0,0 +1,33 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + public function SetCode($code){ + $this->Code=$code; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + $request['code']=$this->Code; + return $request; + } + public function verify(){ + $voice=new voice($this->configs); + return $voice->verify($this->buildRequest()); + } + + } \ No newline at end of file diff --git a/extend/sms/submail/lib/voicexsend.php b/extend/sms/submail/lib/voicexsend.php new file mode 100644 index 0000000..c778d2d --- /dev/null +++ b/extend/sms/submail/lib/voicexsend.php @@ -0,0 +1,52 @@ +configs=$configs; + } + + public function SetTo($address){ + $this->To=trim($address); + } + + public function SetProject($project){ + $this->Project=$project; + } + + public function AddVar($key,$val){ + $this->Vars[$key]=$val; + } + + public function buildRequest(){ + $request=array(); + $request['to']=$this->To; + if(!empty($this->Addressbook)){ + $request['addressbook']=''; + foreach($this->Addressbook as $tmp){ + $request['addressbook'].=$tmp.','; + } + $request['addressbook'] = substr($request['addressbook'],0,count($request['addressbook'])-2); + } + + $request['project']=$this->Project; + if(!empty($this->Vars)){ + $request['vars']=json_encode($this->Vars); + } + return $request; + } + public function xsend(){ + $voice=new voice($this->configs); + return $voice->xsend($this->buildRequest()); + } + + } diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..8c33ea1 Binary files /dev/null and b/favicon.ico differ diff --git a/index.php b/index.php new file mode 100644 index 0000000..83511de --- /dev/null +++ b/index.php @@ -0,0 +1,11 @@ +run()->send(); diff --git a/route/demo.php b/route/demo.php new file mode 100644 index 0000000..931d882 --- /dev/null +++ b/route/demo.php @@ -0,0 +1,20 @@ + 404, 'msg' => '演示环境禁修改菜单权限!']); +}); +Route::post('project/menu/menuEdit', function () { + return json(['code' => 404, 'msg' => '演示环境禁修改菜单权限!']); +}); +Route::post('project/menu/menuDel', function () { + return json(['code' => 404, 'msg' => '演示环境禁修改菜单权限!']); +}); +Route::post('project/index/editPassword', function () { + return json(['code' => 404, 'msg' => '演示环境禁修改密码!']); +}); +//Route::post('project/account/del', function () { +// return json(['code' => 404, 'msg' => '演示环境禁修改账号!']); +//}); +return []; diff --git a/route/route.php b/route/route.php new file mode 100644 index 0000000..5f75042 --- /dev/null +++ b/route/route.php @@ -0,0 +1,14 @@ +initialize(); +Console::init(); diff --git a/thinkphp/.gitignore b/thinkphp/.gitignore new file mode 100644 index 0000000..f7775ba --- /dev/null +++ b/thinkphp/.gitignore @@ -0,0 +1,8 @@ +/vendor +composer.phar +composer.lock +.DS_Store +Thumbs.db +/phpunit.xml +/.idea +/.vscode \ No newline at end of file diff --git a/thinkphp/.htaccess b/thinkphp/.htaccess new file mode 100644 index 0000000..3418e55 --- /dev/null +++ b/thinkphp/.htaccess @@ -0,0 +1 @@ +deny from all \ No newline at end of file diff --git a/thinkphp/CONTRIBUTING.md b/thinkphp/CONTRIBUTING.md new file mode 100644 index 0000000..6cefcb3 --- /dev/null +++ b/thinkphp/CONTRIBUTING.md @@ -0,0 +1,119 @@ +如何贡献我的源代码 +=== + +此文档介绍了 ThinkPHP 团队的组成以及运转机制,您提交的代码将给 ThinkPHP 项目带来什么好处,以及如何才能加入我们的行列。 + +## 通过 Github 贡献代码 + +ThinkPHP 目前使用 Git 来控制程序版本,如果你想为 ThinkPHP 贡献源代码,请先大致了解 Git 的使用方法。我们目前把项目托管在 GitHub 上,任何 GitHub 用户都可以向我们贡献代码。 + +参与的方式很简单,`fork`一份 ThinkPHP 的代码到你的仓库中,修改后提交,并向我们发起`pull request`申请,我们会及时对代码进行审查并处理你的申请并。审查通过后,你的代码将被`merge`进我们的仓库中,这样你就会自动出现在贡献者名单里了,非常方便。 + +我们希望你贡献的代码符合: + +* ThinkPHP 的编码规范 +* 适当的注释,能让其他人读懂 +* 遵循 Apache2 开源协议 + +**如果想要了解更多细节或有任何疑问,请继续阅读下面的内容** + +### 注意事项 + +* 本项目代码格式化标准选用 [**PSR-2**](http://www.kancloud.cn/thinkphp/php-fig-psr/3141); +* 类名和类文件名遵循 [**PSR-4**](http://www.kancloud.cn/thinkphp/php-fig-psr/3144); +* 对于 Issues 的处理,请使用诸如 `fix #xxx(Issue ID)` 的 commit title 直接关闭 issue。 +* 系统会自动在 PHP 5.4 5.5 5.6 7.0 和 HHVM 上测试修改,其中 HHVM 下的测试容许报错,请确保你的修改符合 PHP 5.4 ~ 5.6 和 PHP 7.0 的语法规范; +* 管理员不会合并造成 CI faild 的修改,若出现 CI faild 请检查自己的源代码或修改相应的[单元测试文件](tests); + +## GitHub Issue + +GitHub 提供了 Issue 功能,该功能可以用于: + +* 提出 bug +* 提出功能改进 +* 反馈使用体验 + +该功能不应该用于: + + * 提出修改意见(涉及代码署名和修订追溯问题) + * 不友善的言论 + +## 快速修改 + +**GitHub 提供了快速编辑文件的功能** + +1. 登录 GitHub 帐号; +2. 浏览项目文件,找到要进行修改的文件; +3. 点击右上角铅笔图标进行修改; +4. 填写 `Commit changes` 相关内容(Title 必填); +5. 提交修改,等待 CI 验证和管理员合并。 + +**若您需要一次提交大量修改,请继续阅读下面的内容** + +## 完整流程 + +1. `fork`本项目; +2. 克隆(`clone`)你 `fork` 的项目到本地; +3. 新建分支(`branch`)并检出(`checkout`)新分支; +4. 添加本项目到你的本地 git 仓库作为上游(`upstream`); +5. 进行修改,若你的修改包含方法或函数的增减,请记得修改[单元测试文件](tests); +6. 变基(衍合 `rebase`)你的分支到上游 master 分支; +7. `push` 你的本地仓库到 GitHub; +8. 提交 `pull request`; +9. 等待 CI 验证(若不通过则重复 5~7,GitHub 会自动更新你的 `pull request`); +10. 等待管理员处理,并及时 `rebase` 你的分支到上游 master 分支(若上游 master 分支有修改)。 + +*若有必要,可以 `git push -f` 强行推送 rebase 后的分支到自己的 `fork`* + +*绝对不可以使用 `git push -f` 强行推送修改到上游* + +### 注意事项 + +* 若对上述流程有任何不清楚的地方,请查阅 GIT 教程,如 [这个](http://backlogtool.com/git-guide/cn/); +* 对于代码**不同方面**的修改,请在自己 `fork` 的项目中**创建不同的分支**(原因参见`完整流程`第9条备注部分); +* 变基及交互式变基操作参见 [Git 交互式变基](http://pakchoi.me/2015/03/17/git-interactive-rebase/) + +## 推荐资源 + +### 开发环境 + +* XAMPP for Windows 5.5.x +* WampServer (for Windows) +* upupw Apache PHP5.4 ( for Windows) + +或自行安装 + +- Apache / Nginx +- PHP 5.4 ~ 5.6 +- MySQL / MariaDB + +*Windows 用户推荐添加 PHP bin 目录到 PATH,方便使用 composer* + +*Linux 用户自行配置环境, Mac 用户推荐使用内置 Apache 配合 Homebrew 安装 PHP 和 MariaDB* + +### 编辑器 + +Sublime Text 3 + phpfmt 插件 + +phpfmt 插件参数 + +```json +{ + "autocomplete": true, + "enable_auto_align": true, + "format_on_save": true, + "indent_with_space": true, + "psr1_naming": false, + "psr2": true, + "version": 4 +} +``` + +或其他 编辑器 / IDE 配合 PSR2 自动格式化工具 + +### Git GUI + +* SourceTree +* GitHub Desktop + +或其他 Git 图形界面客户端 diff --git a/thinkphp/LICENSE.txt b/thinkphp/LICENSE.txt new file mode 100644 index 0000000..774fa76 --- /dev/null +++ b/thinkphp/LICENSE.txt @@ -0,0 +1,32 @@ + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) +All rights reserved。 +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +Apache Licence是著名的非盈利开源组织Apache采用的协议。 +该协议和BSD类似,鼓励代码共享和尊重原作者的著作权, +允许代码修改,再作为开源或商业软件发布。需要满足 +的条件: +1. 需要给代码的用户一份Apache Licence ; +2. 如果你修改了代码,需要在被修改的文件中说明; +3. 在延伸的代码中(修改和有源代码衍生的代码中)需要 +带有原来代码中的协议,商标,专利声明和其他原来作者规 +定需要包含的说明; +4. 如果再发布的产品中包含一个Notice文件,则在Notice文 +件中需要带有本协议内容。你可以在Notice中增加自己的 +许可,但不可以表现为对Apache Licence构成更改。 +具体的协议参考:http://www.apache.org/licenses/LICENSE-2.0 + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. diff --git a/thinkphp/README.md b/thinkphp/README.md new file mode 100644 index 0000000..b2a791b --- /dev/null +++ b/thinkphp/README.md @@ -0,0 +1,93 @@ +![](https://box.kancloud.cn/5a0aaa69a5ff42657b5c4715f3d49221) + +ThinkPHP 5.1(LTS) —— 12载初心,你值得信赖的PHP框架 +=============== + +[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/top-think/framework/badges/quality-score.png?b=5.1)](https://scrutinizer-ci.com/g/top-think/framework/?branch=5.1) +[![Build Status](https://travis-ci.org/top-think/framework.svg?branch=master)](https://travis-ci.org/top-think/framework) +[![Total Downloads](https://poser.pugx.org/topthink/framework/downloads)](https://packagist.org/packages/topthink/framework) +[![Latest Stable Version](https://poser.pugx.org/topthink/framework/v/stable)](https://packagist.org/packages/topthink/framework) +[![PHP Version](https://img.shields.io/badge/php-%3E%3D5.6-8892BF.svg)](http://www.php.net/) +[![License](https://poser.pugx.org/topthink/framework/license)](https://packagist.org/packages/topthink/framework) + +ThinkPHP5.1对底层架构做了进一步的改进,减少依赖,其主要特性包括: + + + 采用容器统一管理对象 + + 支持Facade + + 更易用的路由 + + 注解路由支持 + + 路由跨域请求支持 + + 验证类增强 + + 配置和路由目录独立 + + 取消系统常量 + + 类库别名机制 + + 模型和数据库增强 + + 依赖注入完善 + + 支持PSR-3日志规范 + + 中间件支持(`V5.1.6+`) + + 支持`Swoole`/`Workerman`运行(`V5.1.18+`) + +官方已经正式宣布`5.1.27`版本为LTS版本。 + +### 废除的功能: + + + 聚合模型 + + 内置控制器扩展类 + + 模型自动验证 + +> ThinkPHP5.1的运行环境要求PHP5.6+。 + + +## 安装 + +使用composer安装 + +~~~ +composer create-project topthink/think tp +~~~ + +启动服务 + +~~~ +cd tp +php think run +~~~ + +然后就可以在浏览器中访问 + +~~~ +http://localhost:8000 +~~~ + +更新框架 +~~~ +composer update topthink/framework +~~~ + + +## 在线手册 + ++ [完全开发手册](https://www.kancloud.cn/manual/thinkphp5_1/content) ++ [升级指导](https://www.kancloud.cn/manual/thinkphp5_1/354155) + +## 命名规范 + +`ThinkPHP5.1`遵循PSR-2命名规范和PSR-4自动加载规范。 + +## 参与开发 + +请参阅 [ThinkPHP5 核心框架包](https://github.com/top-think/framework)。 + +## 版权信息 + +ThinkPHP遵循Apache2开源协议发布,并提供免费使用。 + +本项目包含的第三方源码和二进制文件之版权信息另行标注。 + +版权所有Copyright © 2006-2018 by ThinkPHP (http://thinkphp.cn) + +All rights reserved。 + +ThinkPHP® 商标和著作权所有者为上海顶想信息科技有限公司。 + +更多细节参阅 [LICENSE.txt](LICENSE.txt) diff --git a/thinkphp/base.php b/thinkphp/base.php new file mode 100644 index 0000000..d7238cc --- /dev/null +++ b/thinkphp/base.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- +namespace think; + +// 载入Loader类 +require __DIR__ . '/library/think/Loader.php'; + +// 注册自动加载 +Loader::register(); + +// 注册错误和异常处理机制 +Error::register(); + +// 实现日志接口 +if (interface_exists('Psr\Log\LoggerInterface')) { + interface LoggerInterface extends \Psr\Log\LoggerInterface + {} +} else { + interface LoggerInterface + {} +} + +// 注册类库别名 +Loader::addClassAlias([ + 'App' => facade\App::class, + 'Build' => facade\Build::class, + 'Cache' => facade\Cache::class, + 'Config' => facade\Config::class, + 'Cookie' => facade\Cookie::class, + 'Db' => Db::class, + 'Debug' => facade\Debug::class, + 'Env' => facade\Env::class, + 'Facade' => Facade::class, + 'Hook' => facade\Hook::class, + 'Lang' => facade\Lang::class, + 'Log' => facade\Log::class, + 'Request' => facade\Request::class, + 'Response' => facade\Response::class, + 'Route' => facade\Route::class, + 'Session' => facade\Session::class, + 'Url' => facade\Url::class, + 'Validate' => facade\Validate::class, + 'View' => facade\View::class, +]); diff --git a/thinkphp/composer.json b/thinkphp/composer.json new file mode 100644 index 0000000..cc4fca9 --- /dev/null +++ b/thinkphp/composer.json @@ -0,0 +1,35 @@ +{ + "name": "topthink/framework", + "description": "the new thinkphp framework", + "type": "think-framework", + "keywords": [ + "framework", + "thinkphp", + "ORM" + ], + "homepage": "http://thinkphp.cn/", + "license": "Apache-2.0", + "authors": [ + { + "name": "liu21st", + "email": "liu21st@gmail.com" + }, + { + "name": "yunwuxin", + "email": "448901948@qq.com" + } + ], + "require": { + "php": ">=5.6.0", + "topthink/think-installer": "2.*" + }, + "require-dev": { + "phpunit/phpunit": "^5.0|^6.0", + "johnkary/phpunit-speedtrap": "^1.0", + "mikey179/vfsStream": "~1.6", + "phploc/phploc": "2.*", + "sebastian/phpcpd": "2.*", + "squizlabs/php_codesniffer": "2.*", + "phpdocumentor/reflection-docblock": "^2.0" + } +} diff --git a/thinkphp/convention.php b/thinkphp/convention.php new file mode 100644 index 0000000..1d85e56 --- /dev/null +++ b/thinkphp/convention.php @@ -0,0 +1,327 @@ + [ + // 应用名称 + 'app_name' => '', + // 应用地址 + 'app_host' => '', + // 应用调试模式 + 'app_debug' => false, + // 应用Trace + 'app_trace' => false, + // 应用模式状态 + 'app_status' => '', + // 是否HTTPS + 'is_https' => false, + // 入口自动绑定模块 + 'auto_bind_module' => false, + // 注册的根命名空间 + 'root_namespace' => [], + // 默认输出类型 + 'default_return_type' => 'html', + // 默认AJAX 数据返回格式,可选json xml ... + 'default_ajax_return' => 'json', + // 默认JSONP格式返回的处理方法 + 'default_jsonp_handler' => 'jsonpReturn', + // 默认JSONP处理方法 + 'var_jsonp_handler' => 'callback', + // 默认时区 + 'default_timezone' => 'Asia/Shanghai', + // 是否开启多语言 + 'lang_switch_on' => false, + // 默认验证器 + 'default_validate' => '', + // 默认语言 + 'default_lang' => 'zh-cn', + + // +---------------------------------------------------------------------- + // | 模块设置 + // +---------------------------------------------------------------------- + + // 自动搜索控制器 + 'controller_auto_search' => false, + // 操作方法前缀 + 'use_action_prefix' => false, + // 操作方法后缀 + 'action_suffix' => '', + // 默认的空控制器名 + 'empty_controller' => 'Error', + // 默认的空模块名 + 'empty_module' => '', + // 默认模块名 + 'default_module' => 'index', + // 是否支持多模块 + 'app_multi_module' => true, + // 禁止访问模块 + 'deny_module_list' => ['common'], + // 默认控制器名 + 'default_controller' => 'Index', + // 默认操作名 + 'default_action' => 'index', + // 是否自动转换URL中的控制器和操作名 + 'url_convert' => true, + // 默认的访问控制器层 + 'url_controller_layer' => 'controller', + // 应用类库后缀 + 'class_suffix' => false, + // 控制器类后缀 + 'controller_suffix' => false, + + // +---------------------------------------------------------------------- + // | URL请求设置 + // +---------------------------------------------------------------------- + + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'HTTP_X_REAL_IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // 是否开启请求缓存 true自动缓存 支持设置请求缓存规则 + 'request_cache' => false, + // 请求缓存有效期 + 'request_cache_expire' => null, + // 全局请求缓存排除规则 + 'request_cache_except' => [], + + // +---------------------------------------------------------------------- + // | 路由设置 + // +---------------------------------------------------------------------- + + // pathinfo分隔符 + 'pathinfo_depr' => '/', + // URL普通方式参数 用于自动生成 + 'url_common_param' => false, + // URL参数方式 0 按名称成对解析 1 按顺序解析 + 'url_param_type' => 0, + // 是否开启路由延迟解析 + 'url_lazy_route' => false, + // 是否强制使用路由 + 'url_route_must' => false, + // 合并路由规则 + 'route_rule_merge' => false, + // 路由是否完全匹配 + 'route_complete_match' => false, + // 使用注解路由 + 'route_annotation' => false, + // 默认的路由变量规则 + 'default_route_pattern' => '\w+', + // 是否开启路由缓存 + 'route_check_cache' => false, + // 路由缓存的Key自定义设置(闭包),默认为当前URL和请求类型的md5 + 'route_check_cache_key' => '', + // 路由缓存的设置 + 'route_cache_option' => [], + + // +---------------------------------------------------------------------- + // | 异常及错误设置 + // +---------------------------------------------------------------------- + + // 默认跳转页面对应的模板文件 + 'dispatch_success_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + 'dispatch_error_tmpl' => __DIR__ . '/tpl/dispatch_jump.tpl', + // 异常页面的模板文件 + 'exception_tmpl' => __DIR__ . '/tpl/think_exception.tpl', + // 错误显示信息,非调试模式有效 + 'error_message' => '页面错误!请稍后再试~', + // 显示错误信息 + 'show_error_msg' => false, + // 异常处理handle类 留空使用 \think\exception\Handle + 'exception_handle' => '', + ], + + // +---------------------------------------------------------------------- + // | 模板设置 + // +---------------------------------------------------------------------- + + 'template' => [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 模板引擎类型 支持 php think 支持扩展 + 'type' => 'Think', + // 视图基础目录,配置目录为所有模块的视图起始目录 + 'view_base' => '', + // 当前模板的视图目录 留空为自动获取 + 'view_path' => '', + // 模板后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 模板引擎普通标签开始标记 + 'tpl_begin' => '{', + // 模板引擎普通标签结束标记 + 'tpl_end' => '}', + // 标签库标签开始标记 + 'taglib_begin' => '{', + // 标签库标签结束标记 + 'taglib_end' => '}', + ], + + // +---------------------------------------------------------------------- + // | 日志设置 + // +---------------------------------------------------------------------- + + 'log' => [ + // 日志记录方式,内置 file socket 支持扩展 + 'type' => 'File', + // 日志保存目录 + //'path' => LOG_PATH, + // 日志记录级别 + 'level' => [], + // 是否记录trace信息到日志 + 'record_trace' => false, + // 是否JSON格式记录 + 'json' => false, + ], + + // +---------------------------------------------------------------------- + // | Trace设置 开启 app_trace 后 有效 + // +---------------------------------------------------------------------- + + 'trace' => [ + // 内置Html Console 支持扩展 + 'type' => 'Html', + 'file' => __DIR__ . '/tpl/page_trace.tpl', + ], + + // +---------------------------------------------------------------------- + // | 缓存设置 + // +---------------------------------------------------------------------- + + 'cache' => [ + // 驱动方式 + 'type' => 'File', + // 缓存保存目录 + //'path' => CACHE_PATH, + // 缓存前缀 + 'prefix' => '', + // 缓存有效期 0表示永久缓存 + 'expire' => 0, + ], + + // +---------------------------------------------------------------------- + // | 会话设置 + // +---------------------------------------------------------------------- + + 'session' => [ + 'id' => '', + // SESSION_ID的提交变量,解决flash上传跨域 + 'var_session_id' => '', + // SESSION 前缀 + 'prefix' => 'think', + // 驱动方式 支持redis memcache memcached + 'type' => '', + // 是否自动开启 SESSION + 'auto_start' => true, + 'httponly' => true, + 'secure' => false, + ], + + // +---------------------------------------------------------------------- + // | Cookie设置 + // +---------------------------------------------------------------------- + + 'cookie' => [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => '', + // 是否使用 setcookie + 'setcookie' => true, + ], + + // +---------------------------------------------------------------------- + // | 数据库设置 + // +---------------------------------------------------------------------- + + 'database' => [ + // 数据库类型 + 'type' => 'mysql', + // 数据库连接DSN配置 + 'dsn' => '', + // 服务器地址 + 'hostname' => '127.0.0.1', + // 数据库名 + 'database' => '', + // 数据库用户名 + 'username' => 'root', + // 数据库密码 + 'password' => '', + // 数据库连接端口 + 'hostport' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => 'array', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // 查询对象 + 'query' => '\\think\\db\\Query', + ], + + //分页配置 + 'paginate' => [ + 'type' => 'bootstrap', + 'var_page' => 'page', + 'list_rows' => 15, + ], + + //控制台配置 + 'console' => [ + 'name' => 'Think Console', + 'version' => '0.1', + 'user' => null, + 'auto_path' => '', + ], + + // 中间件配置 + 'middleware' => [ + 'default_namespace' => 'app\\http\\middleware\\', + ], +]; diff --git a/thinkphp/helper.php b/thinkphp/helper.php new file mode 100644 index 0000000..9c46533 --- /dev/null +++ b/thinkphp/helper.php @@ -0,0 +1,720 @@ + +// +---------------------------------------------------------------------- + +//------------------------ +// ThinkPHP 助手函数 +//------------------------- + +use think\Container; +use think\Db; +use think\exception\HttpException; +use think\exception\HttpResponseException; +use think\facade\Cache; +use think\facade\Config; +use think\facade\Cookie; +use think\facade\Debug; +use think\facade\Env; +use think\facade\Hook; +use think\facade\Lang; +use think\facade\Log; +use think\facade\Request; +use think\facade\Route; +use think\facade\Session; +use think\facade\Url; +use think\Response; +use think\route\RuleItem; + +if (!function_exists('abort')) { + /** + * 抛出HTTP异常 + * @param integer|Response $code 状态码 或者 Response对象实例 + * @param string $message 错误信息 + * @param array $header 参数 + */ + function abort($code, $message = null, $header = []) + { + if ($code instanceof Response) { + throw new HttpResponseException($code); + } else { + throw new HttpException($code, $message, null, $header); + } + } +} + +if (!function_exists('action')) { + /** + * 调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + */ + function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + return app()->action($url, $vars, $layer, $appendSuffix); + } +} + +if (!function_exists('app')) { + /** + * 快速获取容器中的实例 支持依赖注入 + * @param string $name 类名或标识 默认获取当前应用实例 + * @param array $args 参数 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed|\think\App + */ + function app($name = 'think\App', $args = [], $newInstance = false) + { + return Container::get($name, $args, $newInstance); + } +} + +if (!function_exists('behavior')) { + /** + * 执行某个行为(run方法) 支持依赖注入 + * @param mixed $behavior 行为类名或者别名 + * @param mixed $args 参数 + * @return mixed + */ + function behavior($behavior, $args = null) + { + return Hook::exec($behavior, $args); + } +} + +if (!function_exists('bind')) { + /** + * 绑定一个类到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + function bind($abstract, $concrete = null) + { + return Container::getInstance()->bindTo($abstract, $concrete); + } +} + +if (!function_exists('cache')) { + /** + * 缓存管理 + * @param mixed $name 缓存名称,如果为数组表示进行缓存设置 + * @param mixed $value 缓存值 + * @param mixed $options 缓存参数 + * @param string $tag 缓存标签 + * @return mixed + */ + function cache($name, $value = '', $options = null, $tag = null) + { + if (is_array($options)) { + // 缓存操作的同时初始化 + Cache::connect($options); + } elseif (is_array($name)) { + // 缓存初始化 + return Cache::connect($name); + } + + if ('' === $value) { + // 获取缓存 + return 0 === strpos($name, '?') ? Cache::has(substr($name, 1)) : Cache::get($name); + } elseif (is_null($value)) { + // 删除缓存 + return Cache::rm($name); + } + + // 缓存数据 + if (is_array($options)) { + $expire = isset($options['expire']) ? $options['expire'] : null; //修复查询缓存无法设置过期时间 + } else { + $expire = is_numeric($options) ? $options : null; //默认快捷缓存设置过期时间 + } + + if (is_null($tag)) { + return Cache::set($name, $value, $expire); + } else { + return Cache::tag($tag)->set($name, $value, $expire); + } + } +} + +if (!function_exists('call')) { + /** + * 调用反射执行callable 支持依赖注入 + * @param mixed $callable 支持闭包等callable写法 + * @param array $args 参数 + * @return mixed + */ + function call($callable, $args = []) + { + return Container::getInstance()->invoke($callable, $args); + } +} + +if (!function_exists('class_basename')) { + /** + * 获取类名(不包含命名空间) + * + * @param string|object $class + * @return string + */ + function class_basename($class) + { + $class = is_object($class) ? get_class($class) : $class; + return basename(str_replace('\\', '/', $class)); + } +} + +if (!function_exists('class_uses_recursive')) { + /** + *获取一个类里所有用到的trait,包括父类的 + * + * @param $class + * @return array + */ + function class_uses_recursive($class) + { + if (is_object($class)) { + $class = get_class($class); + } + + $results = []; + $classes = array_merge([$class => $class], class_parents($class)); + foreach ($classes as $class) { + $results += trait_uses_recursive($class); + } + + return array_unique($results); + } +} + +if (!function_exists('config')) { + /** + * 获取和设置配置参数 + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + function config($name = '', $value = null) + { + if (is_null($value) && is_string($name)) { + if ('.' == substr($name, -1)) { + return Config::pull(substr($name, 0, -1)); + } + + return 0 === strpos($name, '?') ? Config::has(substr($name, 1)) : Config::get($name); + } else { + return Config::set($name, $value); + } + } +} + +if (!function_exists('container')) { + /** + * 获取容器对象实例 + * @return Container + */ + function container() + { + return Container::getInstance(); + } +} + +if (!function_exists('controller')) { + /** + * 实例化控制器 格式:[模块/]控制器 + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Controller + */ + function controller($name, $layer = 'controller', $appendSuffix = false) + { + return app()->controller($name, $layer, $appendSuffix); + } +} + +if (!function_exists('cookie')) { + /** + * Cookie管理 + * @param string|array $name cookie名称,如果为数组表示进行cookie设置 + * @param mixed $value cookie值 + * @param mixed $option 参数 + * @return mixed + */ + function cookie($name, $value = '', $option = null) + { + if (is_array($name)) { + // 初始化 + Cookie::init($name); + } elseif (is_null($name)) { + // 清除 + Cookie::clear($value); + } elseif ('' === $value) { + // 获取 + return 0 === strpos($name, '?') ? Cookie::has(substr($name, 1), $option) : Cookie::get($name); + } elseif (is_null($value)) { + // 删除 + return Cookie::delete($name); + } else { + // 设置 + return Cookie::set($name, $value, $option); + } + } +} + +if (!function_exists('db')) { + /** + * 实例化数据库类 + * @param string $name 操作的数据表名称(不含前缀) + * @param array|string $config 数据库配置参数 + * @param bool $force 是否强制重新连接 + * @return \think\db\Query + */ + function db($name = '', $config = [], $force = true) + { + return Db::connect($config, $force)->name($name); + } +} + +if (!function_exists('debug')) { + /** + * 记录时间(微秒)和内存使用情况 + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 如果是m 表示统计内存占用 + * @return mixed + */ + function debug($start, $end = '', $dec = 6) + { + if ('' == $end) { + Debug::remark($start); + } else { + return 'm' == $dec ? Debug::getRangeMem($start, $end) : Debug::getRangeTime($start, $end, $dec); + } + } +} + +if (!function_exists('download')) { + /** + * 获取\think\response\Download对象实例 + * @param string $filename 要下载的文件 + * @param string $name 显示文件名 + * @param bool $content 是否为内容 + * @param integer $expire 有效期(秒) + * @return \think\response\Download + */ + function download($filename, $name = '', $content = false, $expire = 360, $openinBrower = false) + { + return Response::create($filename, 'download')->name($name)->isContent($content)->expire($expire)->openinBrower($openinBrower); + } +} + +if (!function_exists('dump')) { + /** + * 浏览器友好的变量输出 + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @return void|string + */ + function dump($var, $echo = true, $label = null) + { + return Debug::dump($var, $echo, $label); + } +} + +if (!function_exists('env')) { + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名(支持二级 .号分割) + * @param string $default 默认值 + * @return mixed + */ + function env($name = null, $default = null) + { + return Env::get($name, $default); + } +} + +if (!function_exists('exception')) { + /** + * 抛出异常处理 + * + * @param string $msg 异常消息 + * @param integer $code 异常代码 默认为0 + * @param string $exception 异常类 + * + * @throws Exception + */ + function exception($msg, $code = 0, $exception = '') + { + $e = $exception ?: '\think\Exception'; + throw new $e($msg, $code); + } +} + +if (!function_exists('halt')) { + /** + * 调试变量并且中断输出 + * @param mixed $var 调试变量或者信息 + */ + function halt($var) + { + dump($var); + + throw new HttpResponseException(new Response); + } +} + +if (!function_exists('input')) { + /** + * 获取输入数据 支持默认值和过滤 + * @param string $key 获取的变量名 + * @param mixed $default 默认值 + * @param string $filter 过滤方法 + * @return mixed + */ + function input($key = '', $default = null, $filter = '') + { + if (0 === strpos($key, '?')) { + $key = substr($key, 1); + $has = true; + } + + if ($pos = strpos($key, '.')) { + // 指定参数来源 + $method = substr($key, 0, $pos); + if (in_array($method, ['get', 'post', 'put', 'patch', 'delete', 'route', 'param', 'request', 'session', 'cookie', 'server', 'env', 'path', 'file'])) { + $key = substr($key, $pos + 1); + } else { + $method = 'param'; + } + } else { + // 默认为自动判断 + $method = 'param'; + } + + if (isset($has)) { + return request()->has($key, $method, $default); + } else { + return request()->$method($key, $default, $filter); + } + } +} + +if (!function_exists('json')) { + /** + * 获取\think\response\Json对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Json + */ + function json($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'json', $code, $header, $options); + } +} + +if (!function_exists('jsonp')) { + /** + * 获取\think\response\Jsonp对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Jsonp + */ + function jsonp($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'jsonp', $code, $header, $options); + } +} + +if (!function_exists('lang')) { + /** + * 获取语言变量值 + * @param string $name 语言变量名 + * @param array $vars 动态变量值 + * @param string $lang 语言 + * @return mixed + */ + function lang($name, $vars = [], $lang = '') + { + return Lang::get($name, $vars, $lang); + } +} + +if (!function_exists('model')) { + /** + * 实例化Model + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Model + */ + function model($name = '', $layer = 'model', $appendSuffix = false) + { + return app()->model($name, $layer, $appendSuffix); + } +} + +if (!function_exists('parse_name')) { + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + function parse_name($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + + return $ucfirst ? ucfirst($name) : lcfirst($name); + } else { + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + } +} + +if (!function_exists('redirect')) { + /** + * 获取\think\response\Redirect对象实例 + * @param mixed $url 重定向地址 支持Url::build方法的地址 + * @param array|integer $params 额外参数 + * @param integer $code 状态码 + * @return \think\response\Redirect + */ + function redirect($url = [], $params = [], $code = 302) + { + if (is_integer($params)) { + $code = $params; + $params = []; + } + + return Response::create($url, 'redirect', $code)->params($params); + } +} + +if (!function_exists('request')) { + /** + * 获取当前Request对象实例 + * @return Request + */ + function request() + { + return app('request'); + } +} + +if (!function_exists('response')) { + /** + * 创建普通 Response 对象实例 + * @param mixed $data 输出数据 + * @param int|string $code 状态码 + * @param array $header 头信息 + * @param string $type + * @return Response + */ + function response($data = [], $code = 200, $header = [], $type = 'html') + { + return Response::create($data, $type, $code, $header); + } +} + +if (!function_exists('route')) { + /** + * 路由注册 + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + function route($rule, $route, $option = [], $pattern = []) + { + return Route::rule($rule, $route, '*', $option, $pattern); + } +} + +if (!function_exists('session')) { + /** + * Session管理 + * @param string|array $name session名称,如果为数组表示进行session设置 + * @param mixed $value session值 + * @param string $prefix 前缀 + * @return mixed + */ + function session($name, $value = '', $prefix = null) + { + if (is_array($name)) { + // 初始化 + Session::init($name); + } elseif (is_null($name)) { + // 清除 + Session::clear($value); + } elseif ('' === $value) { + // 判断或获取 + return 0 === strpos($name, '?') ? Session::has(substr($name, 1), $prefix) : Session::get($name, $prefix); + } elseif (is_null($value)) { + // 删除 + return Session::delete($name, $prefix); + } else { + // 设置 + return Session::set($name, $value, $prefix); + } + } +} + +if (!function_exists('token')) { + /** + * 生成表单令牌 + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + function token($name = '__token__', $type = 'md5') + { + $token = Request::token($name, $type); + + return ''; + } +} + +if (!function_exists('trace')) { + /** + * 记录日志信息 + * @param mixed $log log信息 支持字符串和数组 + * @param string $level 日志级别 + * @return array|void + */ + function trace($log = '[think]', $level = 'log') + { + if ('[think]' === $log) { + return Log::getLog(); + } else { + Log::record($log, $level); + } + } +} + +if (!function_exists('trait_uses_recursive')) { + /** + * 获取一个trait里所有引用到的trait + * + * @param string $trait + * @return array + */ + function trait_uses_recursive($trait) + { + $traits = class_uses($trait); + foreach ($traits as $trait) { + $traits += trait_uses_recursive($trait); + } + + return $traits; + } +} + +if (!function_exists('url')) { + /** + * Url生成 + * @param string $url 路由地址 + * @param string|array $vars 变量 + * @param bool|string $suffix 生成的URL后缀 + * @param bool|string $domain 域名 + * @return string + */ + function url($url = '', $vars = '', $suffix = true, $domain = false) + { + return Url::build($url, $vars, $suffix, $domain); + } +} + +if (!function_exists('validate')) { + /** + * 实例化验证器 + * @param string $name 验证器名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return \think\Validate + */ + function validate($name = '', $layer = 'validate', $appendSuffix = false) + { + return app()->validate($name, $layer, $appendSuffix); + } +} + +if (!function_exists('view')) { + /** + * 渲染模板输出 + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param integer $code 状态码 + * @param callable $filter 内容过滤 + * @return \think\response\View + */ + function view($template = '', $vars = [], $code = 200, $filter = null) + { + return Response::create($template, 'view', $code)->assign($vars)->filter($filter); + } +} + +if (!function_exists('widget')) { + /** + * 渲染输出Widget + * @param string $name Widget名称 + * @param array $data 传入的参数 + * @return mixed + */ + function widget($name, $data = []) + { + return app()->action($name, $data, 'widget'); + } +} + +if (!function_exists('xml')) { + /** + * 获取\think\response\Xml对象实例 + * @param mixed $data 返回的数据 + * @param integer $code 状态码 + * @param array $header 头部 + * @param array $options 参数 + * @return \think\response\Xml + */ + function xml($data = [], $code = 200, $header = [], $options = []) + { + return Response::create($data, 'xml', $code, $header, $options); + } +} + +if (!function_exists('yaconf')) { + /** + * 获取yaconf配置 + * + * @param string $name 配置参数名 + * @param mixed $default 默认值 + * @return mixed + */ + function yaconf($name, $default = null) + { + return Config::yaconf($name, $default); + } +} diff --git a/thinkphp/lang/zh-cn.php b/thinkphp/lang/zh-cn.php new file mode 100644 index 0000000..1e05082 --- /dev/null +++ b/thinkphp/lang/zh-cn.php @@ -0,0 +1,144 @@ + +// +---------------------------------------------------------------------- + +// 核心中文语言包 +return [ + // 系统错误提示 + 'Undefined variable' => '未定义变量', + 'Undefined index' => '未定义数组索引', + 'Undefined offset' => '未定义数组下标', + 'Parse error' => '语法解析错误', + 'Type error' => '类型错误', + 'Fatal error' => '致命错误', + 'syntax error' => '语法错误', + + // 框架核心错误提示 + 'dispatch type not support' => '不支持的调度类型', + 'method param miss' => '方法参数错误', + 'method not exists' => '方法不存在', + 'function not exists' => '函数不存在', + 'file not exists' => '文件不存在', + 'module not exists' => '模块不存在', + 'controller not exists' => '控制器不存在', + 'class not exists' => '类不存在', + 'property not exists' => '类的属性不存在', + 'template not exists' => '模板文件不存在', + 'illegal controller name' => '非法的控制器名称', + 'illegal action name' => '非法的操作名称', + 'url suffix deny' => '禁止的URL后缀访问', + 'Route Not Found' => '当前访问路由未定义或不匹配', + 'Undefined db type' => '未定义数据库类型', + 'variable type error' => '变量类型错误', + 'PSR-4 error' => 'PSR-4 规范错误', + 'not support total' => '简洁模式下不能获取数据总数', + 'not support last' => '简洁模式下不能获取最后一页', + 'error session handler' => '错误的SESSION处理器类', + 'not allow php tag' => '模板不允许使用PHP语法', + 'not support' => '不支持', + 'redisd master' => 'Redisd 主服务器错误', + 'redisd slave' => 'Redisd 从服务器错误', + 'must run at sae' => '必须在SAE运行', + 'memcache init error' => '未开通Memcache服务,请在SAE管理平台初始化Memcache服务', + 'KVDB init error' => '没有初始化KVDB,请在SAE管理平台初始化KVDB服务', + 'fields not exists' => '数据表字段不存在', + 'where express error' => '查询表达式错误', + 'order express error' => '排序表达式错误', + 'no data to update' => '没有任何数据需要更新', + 'miss data to insert' => '缺少需要写入的数据', + 'not support data' => '不支持的数据表达式', + 'miss complex primary data' => '缺少复合主键数据', + 'miss update condition' => '缺少更新条件', + 'model data Not Found' => '模型数据不存在', + 'table data not Found' => '表数据不存在', + 'delete without condition' => '没有条件不会执行删除操作', + 'miss relation data' => '缺少关联表数据', + 'tag attr must' => '模板标签属性必须', + 'tag error' => '模板标签错误', + 'cache write error' => '缓存写入失败', + 'sae mc write error' => 'SAE mc 写入错误', + 'route name not exists' => '路由标识不存在(或参数不够)', + 'invalid request' => '非法请求', + 'bind attr has exists' => '模型的属性已经存在', + 'relation data not exists' => '关联数据不存在', + 'relation not support' => '关联不支持', + 'chunk not support order' => 'Chunk不支持调用order方法', + 'route pattern error' => '路由变量规则定义错误', + 'route behavior will not support' => '路由行为废弃(使用中间件替代)', + 'closure not support cache(true)' => '使用闭包查询不支持cache(true),请指定缓存Key', + + // 上传错误信息 + 'unknown upload error' => '未知上传错误!', + 'file write error' => '文件写入失败!', + 'upload temp dir not found' => '找不到临时文件夹!', + 'no file to uploaded' => '没有文件被上传!', + 'only the portion of file is uploaded' => '文件只有部分被上传!', + 'upload File size exceeds the maximum value' => '上传文件大小超过了最大值!', + 'upload write error' => '文件上传保存错误!', + 'has the same filename: {:filename}' => '存在同名文件:{:filename}', + 'upload illegal files' => '非法上传文件', + 'illegal image files' => '非法图片文件', + 'extensions to upload is not allowed' => '上传文件后缀不允许', + 'mimetype to upload is not allowed' => '上传文件MIME类型不允许!', + 'filesize not match' => '上传文件大小不符!', + 'directory {:path} creation failed' => '目录 {:path} 创建失败!', + + 'The middleware must return Response instance' => '中间件方法必须返回Response对象实例', + 'The queue was exhausted, with no response returned' => '中间件队列为空', + // Validate Error Message + ':attribute require' => ':attribute不能为空', + ':attribute must' => ':attribute必须', + ':attribute must be numeric' => ':attribute必须是数字', + ':attribute must be integer' => ':attribute必须是整数', + ':attribute must be float' => ':attribute必须是浮点数', + ':attribute must be bool' => ':attribute必须是布尔值', + ':attribute not a valid email address' => ':attribute格式不符', + ':attribute not a valid mobile' => ':attribute格式不符', + ':attribute must be a array' => ':attribute必须是数组', + ':attribute must be yes,on or 1' => ':attribute必须是yes、on或者1', + ':attribute not a valid datetime' => ':attribute不是一个有效的日期或时间格式', + ':attribute not a valid file' => ':attribute不是有效的上传文件', + ':attribute not a valid image' => ':attribute不是有效的图像文件', + ':attribute must be alpha' => ':attribute只能是字母', + ':attribute must be alpha-numeric' => ':attribute只能是字母和数字', + ':attribute must be alpha-numeric, dash, underscore' => ':attribute只能是字母、数字和下划线_及破折号-', + ':attribute not a valid domain or ip' => ':attribute不是有效的域名或者IP', + ':attribute must be chinese' => ':attribute只能是汉字', + ':attribute must be chinese or alpha' => ':attribute只能是汉字、字母', + ':attribute must be chinese,alpha-numeric' => ':attribute只能是汉字、字母和数字', + ':attribute must be chinese,alpha-numeric,underscore, dash' => ':attribute只能是汉字、字母、数字和下划线_及破折号-', + ':attribute not a valid url' => ':attribute不是有效的URL地址', + ':attribute not a valid ip' => ':attribute不是有效的IP地址', + ':attribute must be dateFormat of :rule' => ':attribute必须使用日期格式 :rule', + ':attribute must be in :rule' => ':attribute必须在 :rule 范围内', + ':attribute be notin :rule' => ':attribute不能在 :rule 范围内', + ':attribute must between :1 - :2' => ':attribute只能在 :1 - :2 之间', + ':attribute not between :1 - :2' => ':attribute不能在 :1 - :2 之间', + 'size of :attribute must be :rule' => ':attribute长度不符合要求 :rule', + 'max size of :attribute must be :rule' => ':attribute长度不能超过 :rule', + 'min size of :attribute must be :rule' => ':attribute长度不能小于 :rule', + ':attribute cannot be less than :rule' => ':attribute日期不能小于 :rule', + ':attribute cannot exceed :rule' => ':attribute日期不能超过 :rule', + ':attribute not within :rule' => '不在有效期内 :rule', + 'access IP is not allowed' => '不允许的IP访问', + 'access IP denied' => '禁止的IP访问', + ':attribute out of accord with :2' => ':attribute和确认字段:2不一致', + ':attribute cannot be same with :2' => ':attribute和比较字段:2不能相同', + ':attribute must greater than or equal :rule' => ':attribute必须大于等于 :rule', + ':attribute must greater than :rule' => ':attribute必须大于 :rule', + ':attribute must less than or equal :rule' => ':attribute必须小于等于 :rule', + ':attribute must less than :rule' => ':attribute必须小于 :rule', + ':attribute must equal :rule' => ':attribute必须等于 :rule', + ':attribute has exists' => ':attribute已存在', + ':attribute not conform to the rules' => ':attribute不符合指定规则', + 'invalid Request method' => '无效的请求类型', + 'invalid token' => '令牌数据无效', + 'not conform to the rules' => '规则错误', +]; diff --git a/thinkphp/library/think/App.php b/thinkphp/library/think/App.php new file mode 100644 index 0000000..cfa2601 --- /dev/null +++ b/thinkphp/library/think/App.php @@ -0,0 +1,981 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\exception\HttpResponseException; +use think\route\Dispatch; + +/** + * App 应用管理 + */ +class App extends Container +{ + const VERSION = '5.1.31 LTS'; + + /** + * 当前模块路径 + * @var string + */ + protected $modulePath; + + /** + * 应用调试模式 + * @var bool + */ + protected $appDebug = true; + + /** + * 应用开始时间 + * @var float + */ + protected $beginTime; + + /** + * 应用内存初始占用 + * @var integer + */ + protected $beginMem; + + /** + * 应用类库命名空间 + * @var string + */ + protected $namespace = 'app'; + + /** + * 应用类库后缀 + * @var bool + */ + protected $suffix = false; + + /** + * 严格路由检测 + * @var bool + */ + protected $routeMust; + + /** + * 应用类库目录 + * @var string + */ + protected $appPath; + + /** + * 框架目录 + * @var string + */ + protected $thinkPath; + + /** + * 应用根目录 + * @var string + */ + protected $rootPath; + + /** + * 运行时目录 + * @var string + */ + protected $runtimePath; + + /** + * 配置目录 + * @var string + */ + protected $configPath; + + /** + * 路由目录 + * @var string + */ + protected $routePath; + + /** + * 配置后缀 + * @var string + */ + protected $configExt; + + /** + * 应用调度实例 + * @var Dispatch + */ + protected $dispatch; + + /** + * 绑定模块(控制器) + * @var string + */ + protected $bindModule; + + /** + * 初始化 + * @var bool + */ + protected $initialized = false; + + public function __construct($appPath = '') + { + $this->thinkPath = dirname(dirname(__DIR__)) . DIRECTORY_SEPARATOR; + $this->path($appPath); + } + + /** + * 绑定模块或者控制器 + * @access public + * @param string $bind + * @return $this + */ + public function bind($bind) + { + $this->bindModule = $bind; + return $this; + } + + /** + * 设置应用类库目录 + * @access public + * @param string $path 路径 + * @return $this + */ + public function path($path) + { + $this->appPath = $path ? realpath($path) . DIRECTORY_SEPARATOR : $this->getAppPath(); + + return $this; + } + + /** + * 初始化应用 + * @access public + * @return void + */ + public function initialize() + { + if ($this->initialized) { + return; + } + + $this->initialized = true; + $this->beginTime = microtime(true); + $this->beginMem = memory_get_usage(); + + $this->rootPath = dirname($this->appPath) . DIRECTORY_SEPARATOR; + $this->runtimePath = $this->rootPath . 'runtime' . DIRECTORY_SEPARATOR; + $this->routePath = $this->rootPath . 'route' . DIRECTORY_SEPARATOR; + $this->configPath = $this->rootPath . 'config' . DIRECTORY_SEPARATOR; + + static::setInstance($this); + + $this->instance('app', $this); + + $this->configExt = $this->env->get('config_ext', '.php'); + + // 加载惯例配置文件 + $this->config->set(include $this->thinkPath . 'convention.php'); + + // 设置路径环境变量 + $this->env->set([ + 'think_path' => $this->thinkPath, + 'root_path' => $this->rootPath, + 'app_path' => $this->appPath, + 'config_path' => $this->configPath, + 'route_path' => $this->routePath, + 'runtime_path' => $this->runtimePath, + 'extend_path' => $this->rootPath . 'extend' . DIRECTORY_SEPARATOR, + 'vendor_path' => $this->rootPath . 'vendor' . DIRECTORY_SEPARATOR, + ]); + + // 加载环境变量配置文件 + if (is_file($this->rootPath . '.env')) { + $this->env->load($this->rootPath . '.env'); + } + + $this->namespace = $this->env->get('app_namespace', $this->namespace); + $this->env->set('app_namespace', $this->namespace); + + // 注册应用命名空间 + Loader::addNamespace($this->namespace, $this->appPath); + + // 初始化应用 + $this->init(); + + // 开启类名后缀 + $this->suffix = $this->config('app.class_suffix'); + + // 应用调试模式 + $this->appDebug = $this->env->get('app_debug', $this->config('app.app_debug')); + $this->env->set('app_debug', $this->appDebug); + + if (!$this->appDebug) { + ini_set('display_errors', 'Off'); + } elseif (PHP_SAPI != 'cli') { + //重新申请一块比较大的buffer + if (ob_get_level() > 0) { + $output = ob_get_clean(); + } + ob_start(); + if (!empty($output)) { + echo $output; + } + } + + // 注册异常处理类 + if ($this->config('app.exception_handle')) { + Error::setExceptionHandler($this->config('app.exception_handle')); + } + + // 注册根命名空间 + if (!empty($this->config('app.root_namespace'))) { + Loader::addNamespace($this->config('app.root_namespace')); + } + + // 加载composer autofile文件 + Loader::loadComposerAutoloadFiles(); + + // 注册类库别名 + Loader::addClassAlias($this->config->pull('alias')); + + // 数据库配置初始化 + Db::init($this->config->pull('database')); + + // 设置系统时区 + date_default_timezone_set($this->config('app.default_timezone')); + + // 读取语言包 + $this->loadLangPack(); + + // 路由初始化 + $this->routeInit(); + } + + /** + * 初始化应用或模块 + * @access public + * @param string $module 模块名 + * @return void + */ + public function init($module = '') + { + // 定位模块目录 + $module = $module ? $module . DIRECTORY_SEPARATOR : ''; + $path = $this->appPath . $module; + + // 加载初始化文件 + if (is_file($path . 'init.php')) { + include $path . 'init.php'; + } elseif (is_file($this->runtimePath . $module . 'init.php')) { + include $this->runtimePath . $module . 'init.php'; + } else { + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $this->hook->import($tags); + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + include_once $path . 'common.php'; + } + + if ('' == $module) { + // 加载系统助手函数 + include $this->thinkPath . 'helper.php'; + } + + // 加载中间件 + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $this->middleware->import($middleware); + } + } + + // 注册服务的容器对象实例 + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $this->bindTo($provider); + } + } + + // 自动读取配置文件 + if (is_dir($path . 'config')) { + $dir = $path . 'config' . DIRECTORY_SEPARATOR; + } elseif (is_dir($this->configPath . $module)) { + $dir = $this->configPath . $module; + } + + $files = isset($dir) ? scandir($dir) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $this->configExt) { + $this->config->load($dir . $file, pathinfo($file, PATHINFO_FILENAME)); + } + } + } + + $this->setModulePath($path); + + if ($module) { + // 对容器中的对象实例进行配置更新 + $this->containerConfigUpdate($module); + } + } + + protected function containerConfigUpdate($module) + { + $config = $this->config->get(); + + // 注册异常处理类 + if ($config['app']['exception_handle']) { + Error::setExceptionHandler($config['app']['exception_handle']); + } + + Db::init($config['database']); + $this->middleware->setConfig($config['middleware']); + $this->route->setConfig($config['app']); + $this->request->init($config['app']); + $this->cookie->init($config['cookie']); + $this->view->init($config['template']); + $this->log->init($config['log']); + $this->session->setConfig($config['session']); + $this->debug->setConfig($config['trace']); + $this->cache->init($config['cache'], true); + + // 加载当前模块语言包 + $this->lang->load($this->appPath . $module . DIRECTORY_SEPARATOR . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php'); + + // 模块请求缓存检查 + $this->checkRequestCache( + $config['app']['request_cache'], + $config['app']['request_cache_expire'], + $config['app']['request_cache_except'] + ); + } + + /** + * 执行应用程序 + * @access public + * @return Response + * @throws Exception + */ + public function run() + { + try { + // 初始化应用 + $this->initialize(); + + // 监听app_init + $this->hook->listen('app_init'); + + if ($this->bindModule) { + // 模块/控制器绑定 + $this->route->bind($this->bindModule); + } elseif ($this->config('app.auto_bind_module')) { + // 入口自动绑定 + $name = pathinfo($this->request->baseFile(), PATHINFO_FILENAME); + if ($name && 'index' != $name && is_dir($this->appPath . $name)) { + $this->route->bind($name); + } + } + + // 监听app_dispatch + $this->hook->listen('app_dispatch'); + + $dispatch = $this->dispatch; + + if (empty($dispatch)) { + // 路由检测 + $dispatch = $this->routeCheck()->init(); + } + + // 记录当前调度信息 + $this->request->dispatch($dispatch); + + // 记录路由和请求信息 + if ($this->appDebug) { + $this->log('[ ROUTE ] ' . var_export($this->request->routeInfo(), true)); + $this->log('[ HEADER ] ' . var_export($this->request->header(), true)); + $this->log('[ PARAM ] ' . var_export($this->request->param(), true)); + } + + // 监听app_begin + $this->hook->listen('app_begin'); + + // 请求缓存检查 + $this->checkRequestCache( + $this->config('request_cache'), + $this->config('request_cache_expire'), + $this->config('request_cache_except') + ); + + $data = null; + } catch (HttpResponseException $exception) { + $dispatch = null; + $data = $exception->getResponse(); + } + + $this->middleware->add(function (Request $request, $next) use ($dispatch, $data) { + return is_null($data) ? $dispatch->run() : $data; + }); + + $response = $this->middleware->dispatch($this->request); + + // 监听app_end + $this->hook->listen('app_end', $response); + + return $response; + } + + protected function getRouteCacheKey() + { + if ($this->config->get('route_check_cache_key')) { + $closure = $this->config->get('route_check_cache_key'); + $routeKey = $closure($this->request); + } else { + $routeKey = md5($this->request->baseUrl(true) . ':' . $this->request->method()); + } + + return $routeKey; + } + + protected function loadLangPack() + { + // 读取默认语言 + $this->lang->range($this->config('app.default_lang')); + + if ($this->config('app.lang_switch_on')) { + // 开启多语言机制 检测当前语言 + $this->lang->detect(); + } + + $this->request->setLangset($this->lang->range()); + + // 加载系统语言包 + $this->lang->load([ + $this->thinkPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + $this->appPath . 'lang' . DIRECTORY_SEPARATOR . $this->request->langset() . '.php', + ]); + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return void + */ + public function checkRequestCache($key, $expire = null, $except = [], $tag = null) + { + $cache = $this->request->cache($key, $expire, $except, $tag); + + if ($cache) { + $this->setResponseCache($cache); + } + } + + public function setResponseCache($cache) + { + list($key, $expire, $tag) = $cache; + + if (strtotime($this->request->server('HTTP_IF_MODIFIED_SINCE')) + $expire > $this->request->server('REQUEST_TIME')) { + // 读取缓存 + $response = Response::create()->code(304); + throw new HttpResponseException($response); + } elseif ($this->cache->has($key)) { + list($content, $header) = $this->cache->get($key); + + $response = Response::create($content)->header($header); + throw new HttpResponseException($response); + } + } + + /** + * 设置当前请求的调度信息 + * @access public + * @param Dispatch $dispatch 调度信息 + * @return $this + */ + public function dispatch(Dispatch $dispatch) + { + $this->dispatch = $dispatch; + return $this; + } + + /** + * 记录调试信息 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 信息类型 + * @return void + */ + public function log($msg, $type = 'info') + { + $this->appDebug && $this->log->record($msg, $type); + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持二级配置 .号分割) + * @return mixed + */ + public function config($name = '') + { + return $this->config->get($name); + } + + /** + * 路由初始化 导入路由定义规则 + * @access public + * @return void + */ + public function routeInit() + { + // 路由检测 + $files = scandir($this->routePath); + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $this->routePath . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + $this->route->import($rules); + } + } + } + + if ($this->route->config('route_annotation')) { + // 自动生成路由定义 + if ($this->appDebug) { + $suffix = $this->route->config('controller_suffix') || $this->route->config('class_suffix'); + $this->build->buildRoute($suffix); + } + + $filename = $this->runtimePath . 'build_route.php'; + + if (is_file($filename)) { + include $filename; + } + } + } + + /** + * URL路由检测(根据PATH_INFO) + * @access public + * @return Dispatch + */ + public function routeCheck() + { + // 检测路由缓存 + if (!$this->appDebug && $this->config->get('route_check_cache')) { + $routeKey = $this->getRouteCacheKey(); + $option = $this->config->get('route_cache_option'); + + if ($option && $this->cache->connect($option)->has($routeKey)) { + return $this->cache->connect($option)->get($routeKey); + } elseif ($this->cache->has($routeKey)) { + return $this->cache->get($routeKey); + } + } + + // 获取应用调度信息 + $path = $this->request->path(); + + // 是否强制路由模式 + $must = !is_null($this->routeMust) ? $this->routeMust : $this->route->config('url_route_must'); + + // 路由检测 返回一个Dispatch对象 + $dispatch = $this->route->check($path, $must); + + if (!empty($routeKey)) { + try { + if ($option) { + $this->cache->connect($option)->tag('route_cache')->set($routeKey, $dispatch); + } else { + $this->cache->tag('route_cache')->set($routeKey, $dispatch); + } + } catch (\Exception $e) { + // 存在闭包的时候缓存无效 + } + } + + return $dispatch; + } + + /** + * 设置应用的路由检测机制 + * @access public + * @param bool $must 是否强制检测路由 + * @return $this + */ + public function routeMust($must = false) + { + $this->routeMust = $must; + return $this; + } + + /** + * 解析模块和类名 + * @access protected + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return array + */ + protected function parseModuleAndClass($name, $layer, $appendSuffix) + { + if (false !== strpos($name, '\\')) { + $class = $name; + $module = $this->request->module(); + } else { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = $this->request->module(); + } + + $class = $this->parseClass($module, $layer, $name, $appendSuffix); + } + + return [$module, $class]; + } + + /** + * 实例化应用类库 + * @access public + * @param string $name 类名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return object + * @throws ClassNotFoundException + */ + public function create($name, $layer, $appendSuffix = false, $common = 'common') + { + $guid = $name . $layer; + + if ($this->__isset($guid)) { + return $this->__get($guid); + } + + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + $object = $this->__get($class); + } else { + $class = str_replace('\\' . $module . '\\', '\\' . $common . '\\', $class); + if (class_exists($class)) { + $object = $this->__get($class); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } + + $this->__set($guid, $class); + + return $object; + } + + /** + * 实例化(分层)模型 + * @access public + * @param string $name Model名称 + * @param string $layer 业务层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Model + * @throws ClassNotFoundException + */ + public function model($name = '', $layer = 'model', $appendSuffix = false, $common = 'common') + { + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 实例化(分层)控制器 格式:[模块名/]控制器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $empty 空控制器名称 + * @return object + * @throws ClassNotFoundException + */ + public function controller($name, $layer = 'controller', $appendSuffix = false, $empty = '') + { + list($module, $class) = $this->parseModuleAndClass($name, $layer, $appendSuffix); + + if (class_exists($class)) { + return $this->__get($class); + } elseif ($empty && class_exists($emptyClass = $this->parseClass($module, $layer, $empty, $appendSuffix))) { + return $this->__get($emptyClass); + } + + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + + /** + * 实例化验证类 格式:[模块名/]验证器名 + * @access public + * @param string $name 资源地址 + * @param string $layer 验证层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @param string $common 公共模块名 + * @return Validate + * @throws ClassNotFoundException + */ + public function validate($name = '', $layer = 'validate', $appendSuffix = false, $common = 'common') + { + $name = $name ?: $this->config('default_validate'); + + if (empty($name)) { + return new Validate; + } + + return $this->create($name, $layer, $appendSuffix, $common); + } + + /** + * 数据库初始化 + * @access public + * @param mixed $config 数据库配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return \think\db\Query + */ + public function db($config = [], $name = false) + { + return Db::connect($config, $name); + } + + /** + * 远程调用模块的操作方法 参数格式 [模块/控制器/]操作 + * @access public + * @param string $url 调用地址 + * @param string|array $vars 调用参数 支持字符串和数组 + * @param string $layer 要调用的控制层名称 + * @param bool $appendSuffix 是否添加类名后缀 + * @return mixed + * @throws ClassNotFoundException + */ + public function action($url, $vars = [], $layer = 'controller', $appendSuffix = false) + { + $info = pathinfo($url); + $action = $info['basename']; + $module = '.' != $info['dirname'] ? $info['dirname'] : $this->request->controller(); + $class = $this->controller($module, $layer, $appendSuffix); + + if (is_scalar($vars)) { + if (strpos($vars, '=')) { + parse_str($vars, $vars); + } else { + $vars = [$vars]; + } + } + + return $this->invokeMethod([$class, $action . $this->config('action_suffix')], $vars); + } + + /** + * 解析应用类的类名 + * @access public + * @param string $module 模块名 + * @param string $layer 层名 controller model ... + * @param string $name 类名 + * @param bool $appendSuffix + * @return string + */ + public function parseClass($module, $layer, $name, $appendSuffix = false) + { + $name = str_replace(['/', '.'], '\\', $name); + $array = explode('\\', $name); + $class = Loader::parseName(array_pop($array), 1) . ($this->suffix || $appendSuffix ? ucfirst($layer) : ''); + $path = $array ? implode('\\', $array) . '\\' : ''; + + return $this->namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $path . $class; + } + + /** + * 获取框架版本 + * @access public + * @return string + */ + public function version() + { + return static::VERSION; + } + + /** + * 是否为调试模式 + * @access public + * @return bool + */ + public function isDebug() + { + return $this->appDebug; + } + + /** + * 获取模块路径 + * @access public + * @return string + */ + public function getModulePath() + { + return $this->modulePath; + } + + /** + * 设置模块路径 + * @access public + * @param string $path 路径 + * @return void + */ + public function setModulePath($path) + { + $this->modulePath = $path; + $this->env->set('module_path', $path); + } + + /** + * 获取应用根目录 + * @access public + * @return string + */ + public function getRootPath() + { + return $this->rootPath; + } + + /** + * 获取应用类库目录 + * @access public + * @return string + */ + public function getAppPath() + { + if (is_null($this->appPath)) { + $this->appPath = Loader::getRootPath() . 'application' . DIRECTORY_SEPARATOR; + } + + return $this->appPath; + } + + /** + * 获取应用运行时目录 + * @access public + * @return string + */ + public function getRuntimePath() + { + return $this->runtimePath; + } + + /** + * 获取核心框架目录 + * @access public + * @return string + */ + public function getThinkPath() + { + return $this->thinkPath; + } + + /** + * 获取路由目录 + * @access public + * @return string + */ + public function getRoutePath() + { + return $this->routePath; + } + + /** + * 获取应用配置目录 + * @access public + * @return string + */ + public function getConfigPath() + { + return $this->configPath; + } + + /** + * 获取配置后缀 + * @access public + * @return string + */ + public function getConfigExt() + { + return $this->configExt; + } + + /** + * 获取应用类库命名空间 + * @access public + * @return string + */ + public function getNamespace() + { + return $this->namespace; + } + + /** + * 设置应用类库命名空间 + * @access public + * @param string $namespace 命名空间名称 + * @return $this + */ + public function setNamespace($namespace) + { + $this->namespace = $namespace; + return $this; + } + + /** + * 是否启用类库后缀 + * @access public + * @return bool + */ + public function getSuffix() + { + return $this->suffix; + } + + /** + * 获取应用开启时间 + * @access public + * @return float + */ + public function getBeginTime() + { + return $this->beginTime; + } + + /** + * 获取应用初始内存占用 + * @access public + * @return integer + */ + public function getBeginMem() + { + return $this->beginMem; + } + +} diff --git a/thinkphp/library/think/Build.php b/thinkphp/library/think/Build.php new file mode 100644 index 0000000..7a531d7 --- /dev/null +++ b/thinkphp/library/think/Build.php @@ -0,0 +1,415 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Build +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 应用目录 + * @var string + */ + protected $basePath; + + public function __construct(App $app) + { + $this->app = $app; + $this->basePath = $this->app->getAppPath(); + } + + /** + * 根据传入的build资料创建目录和文件 + * @access public + * @param array $build build列表 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + */ + public function run(array $build = [], $namespace = 'app', $suffix = false) + { + // 锁定 + $lockfile = $this->basePath . 'build.lock'; + + if (is_writable($lockfile)) { + return; + } elseif (!touch($lockfile)) { + throw new Exception('应用目录[' . $this->basePath . ']不可写,目录无法自动生成!
请手动生成项目目录~', 10006); + } + + foreach ($build as $module => $list) { + if ('__dir__' == $module) { + // 创建目录列表 + $this->buildDir($list); + } elseif ('__file__' == $module) { + // 创建文件列表 + $this->buildFile($list); + } else { + // 创建模块 + $this->module($module, $list, $namespace, $suffix); + } + } + + // 解除锁定 + unlink($lockfile); + } + + /** + * 创建目录 + * @access protected + * @param array $list 目录列表 + * @return void + */ + protected function buildDir($list) + { + foreach ($list as $dir) { + $this->checkDirBuild($this->basePath . $dir); + } + } + + /** + * 创建文件 + * @access protected + * @param array $list 文件列表 + * @return void + */ + protected function buildFile($list) + { + foreach ($list as $file) { + if (!is_dir($this->basePath . dirname($file))) { + // 创建目录 + mkdir($this->basePath . dirname($file), 0755, true); + } + + if (!is_file($this->basePath . $file)) { + file_put_contents($this->basePath . $file, 'php' == pathinfo($file, PATHINFO_EXTENSION) ? "basePath . $module)) { + // 创建模块目录 + mkdir($this->basePath . $module); + } + + if (basename($this->app->getRuntimePath()) != $module) { + // 创建配置文件和公共文件 + $this->buildCommon($module); + // 创建模块的默认页面 + $this->buildHello($module, $namespace, $suffix); + } + + if (empty($list)) { + // 创建默认的模块目录和文件 + $list = [ + '__file__' => ['common.php'], + '__dir__' => ['controller', 'model', 'view', 'config'], + ]; + } + + // 创建子目录和文件 + foreach ($list as $path => $file) { + $modulePath = $this->basePath . $module . DIRECTORY_SEPARATOR; + if ('__dir__' == $path) { + // 生成子目录 + foreach ($file as $dir) { + $this->checkDirBuild($modulePath . $dir); + } + } elseif ('__file__' == $path) { + // 生成(空白)文件 + foreach ($file as $name) { + if (!is_file($modulePath . $name)) { + file_put_contents($modulePath . $name, 'php' == pathinfo($name, PATHINFO_EXTENSION) ? "checkDirBuild(dirname($filename)); + $content = ''; + break; + default: + // 其他文件 + $content = "app->getNameSpace(); + $content = 'app->config('app.url_controller_layer'); + } + + if ($this->app->config('app.app_multi_module')) { + $modules = glob($this->basePath . '*', GLOB_ONLYDIR); + + foreach ($modules as $module) { + $module = basename($module); + + if (in_array($module, $this->app->config('app.deny_module_list'))) { + continue; + } + + $path = $this->basePath . $module . DIRECTORY_SEPARATOR . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, $module, $suffix, $layer); + } + } else { + $path = $this->basePath . $layer . DIRECTORY_SEPARATOR; + $content .= $this->buildDirRoute($path, $namespace, '', $suffix, $layer); + } + + $filename = $this->app->getRuntimePath() . 'build_route.php'; + file_put_contents($filename, $content); + + return $filename; + } + + /** + * 生成子目录控制器类的路由规则 + * @access protected + * @param string $path 控制器目录 + * @param string $namespace 应用命名空间 + * @param string $module 模块 + * @param bool $suffix 类库后缀 + * @param string $layer 控制器层目录名 + * @return string + */ + protected function buildDirRoute($path, $namespace, $module, $suffix, $layer) + { + $content = ''; + $controllers = glob($path . '*.php'); + + foreach ($controllers as $controller) { + $controller = basename($controller, '.php'); + + $class = new \ReflectionClass($namespace . '\\' . ($module ? $module . '\\' : '') . $layer . '\\' . $controller); + + if (strpos($layer, '\\')) { + // 多级控制器 + $level = str_replace(DIRECTORY_SEPARATOR, '.', substr($layer, 11)); + $controller = $level . '.' . $controller; + $length = strlen(strstr($layer, '\\', true)); + } else { + $length = strlen($layer); + } + + if ($suffix) { + $controller = substr($controller, 0, -$length); + } + + $content .= $this->getControllerRoute($class, $module, $controller); + } + + $subDir = glob($path . '*', GLOB_ONLYDIR); + + foreach ($subDir as $dir) { + $content .= $this->buildDirRoute($dir . DIRECTORY_SEPARATOR, $namespace, $module, $suffix, $layer . '\\' . basename($dir)); + } + + return $content; + } + + /** + * 生成控制器类的路由规则 + * @access protected + * @param string $class 控制器完整类名 + * @param string $module 模块名 + * @param string $controller 控制器名 + * @return string + */ + protected function getControllerRoute($class, $module, $controller) + { + $content = ''; + $comment = $class->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $route = ($module ? $module . '/' : '') . $controller; + $comment = preg_replace('/route\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::resource(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } elseif (false !== strpos($comment, '@alias(')) { + $comment = $this->parseRouteComment($comment, '@alias('); + $route = ($module ? $module . '/' : '') . $controller; + $comment = preg_replace('/alias\(\s?([\'\"][\-\_\/\w]+[\'\"])\s?\)/is', 'Route::alias(\1,\'' . $route . '\')', $comment); + $content .= PHP_EOL . $comment; + } + + $methods = $class->getMethods(\ReflectionMethod::IS_PUBLIC); + + foreach ($methods as $method) { + $comment = $this->getMethodRouteComment($module, $controller, $method); + if ($comment) { + $content .= PHP_EOL . $comment; + } + } + + return $content; + } + + /** + * 解析路由注释 + * @access protected + * @param string $comment + * @param string $tag + * @return string + */ + protected function parseRouteComment($comment, $tag = '@route(') + { + $comment = substr($comment, 3, -2); + $comment = explode(PHP_EOL, substr(strstr(trim($comment), $tag), 1)); + $comment = array_map(function ($item) {return trim(trim($item), ' \t*');}, $comment); + + if (count($comment) > 1) { + $key = array_search('', $comment); + $comment = array_slice($comment, 0, false === $key ? 1 : $key); + } + + $comment = implode(PHP_EOL . "\t", $comment) . ';'; + + if (strpos($comment, '{')) { + $comment = preg_replace_callback('/\{\s?.*?\s?\}/s', function ($matches) { + return false !== strpos($matches[0], '"') ? '[' . substr(var_export(json_decode($matches[0], true), true), 7, -1) . ']' : $matches[0]; + }, $comment); + } + return $comment; + } + + /** + * 获取方法的路由注释 + * @access protected + * @param string $module 模块 + * @param string $controller 控制器名 + * @param \ReflectMethod $reflectMethod + * @return string|void + */ + protected function getMethodRouteComment($module, $controller, $reflectMethod) + { + $comment = $reflectMethod->getDocComment(); + + if (false !== strpos($comment, '@route(')) { + $comment = $this->parseRouteComment($comment); + $action = $reflectMethod->getName(); + + if ($suffix = $this->app->config('app.action_suffix')) { + $action = substr($action, 0, -strlen($suffix)); + } + + $route = ($module ? $module . '/' : '') . $controller . '/' . $action; + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\,?\s?[\'\"]?(\w+?)[\'\"]?\s?\)/is', 'Route::\2(\1,\'' . $route . '\')', $comment); + $comment = preg_replace('/route\s?\(\s?([\'\"][\-\_\/\:\<\>\?\$\[\]\w]+[\'\"])\s?\)/is', 'Route::rule(\1,\'' . $route . '\')', $comment); + + return $comment; + } + } + + /** + * 创建模块的欢迎页面 + * @access protected + * @param string $module 模块名 + * @param string $namespace 应用类库命名空间 + * @param bool $suffix 类库后缀 + * @return void + */ + protected function buildHello($module, $namespace, $suffix = false) + { + $filename = $this->basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'controller' . DIRECTORY_SEPARATOR . 'Index' . ($suffix ? 'Controller' : '') . '.php'; + if (!is_file($filename)) { + $content = file_get_contents($this->app->getThinkPath() . 'tpl' . DIRECTORY_SEPARATOR . 'default_index.tpl'); + $content = str_replace(['{$app}', '{$module}', '{layer}', '{$suffix}'], [$namespace, $module ? $module . '\\' : '', 'controller', $suffix ? 'Controller' : ''], $content); + $this->checkDirBuild(dirname($filename)); + + file_put_contents($filename, $content); + } + } + + /** + * 创建模块的公共文件 + * @access protected + * @param string $module 模块名 + * @return void + */ + protected function buildCommon($module) + { + $filename = $this->app->getConfigPath() . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'app.php'; + $this->checkDirBuild(dirname($filename)); + + if (!is_file($filename)) { + file_put_contents($filename, "basePath . ($module ? $module . DIRECTORY_SEPARATOR : '') . 'common.php'; + + if (!is_file($filename)) { + file_put_contents($filename, " +// +---------------------------------------------------------------------- + +namespace think; + +use think\cache\Driver; + +/** + * Class Cache + * + * @package think + * + * @mixin Driver + * @mixin \think\cache\driver\File + */ +class Cache +{ + /** + * 缓存实例 + * @var array + */ + protected $instance = []; + + /** + * 缓存配置 + * @var array + */ + protected $config = []; + + /** + * 操作句柄 + * @var object + */ + protected $handler; + + public function __construct(array $config = []) + { + $this->config = $config; + $this->init($config); + } + + /** + * 连接缓存 + * @access public + * @param array $options 配置数组 + * @param bool|string $name 缓存连接标识 true 强制重新连接 + * @return Driver + */ + public function connect(array $options = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($options)); + } + + if (true === $name || !isset($this->instance[$name])) { + $type = !empty($options['type']) ? $options['type'] : 'File'; + + if (true === $name) { + $name = md5(serialize($options)); + } + + $this->instance[$name] = Loader::factory($type, '\\think\\cache\\driver\\', $options); + } + + return $this->instance[$name]; + } + + /** + * 自动初始化缓存 + * @access public + * @param array $options 配置数组 + * @param bool $force 强制更新 + * @return Driver + */ + public function init(array $options = [], $force = false) + { + if (is_null($this->handler) || $force) { + + if ('complex' == $options['type']) { + $default = $options['default']; + $options = isset($options[$default['type']]) ? $options[$default['type']] : $default; + } + + $this->handler = $this->connect($options); + } + + return $this->handler; + } + + public static function __make(Config $config) + { + return new static($config->pull('cache')); + } + + public function getConfig() + { + return $this->config; + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 切换缓存类型 需要配置 cache.type 为 complex + * @access public + * @param string $name 缓存标识 + * @return Driver + */ + public function store($name = '') + { + if ('' !== $name && 'complex' == $this->config['type']) { + return $this->connect($this->config[$name], strtolower($name)); + } + + return $this->init(); + } + + public function __call($method, $args) + { + return call_user_func_array([$this->init(), $method], $args); + } + +} diff --git a/thinkphp/library/think/Collection.php b/thinkphp/library/think/Collection.php new file mode 100644 index 0000000..d58c899 --- /dev/null +++ b/thinkphp/library/think/Collection.php @@ -0,0 +1,552 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; + +class Collection implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 数据集数据 + * @var array + */ + protected $items = []; + + public function __construct($items = []) + { + $this->items = $this->convertToArray($items); + } + + public static function make($items = []) + { + return new static($items); + } + + /** + * 是否为空 + * @access public + * @return bool + */ + public function isEmpty() + { + return empty($this->items); + } + + public function toArray() + { + return array_map(function ($value) { + return ($value instanceof Model || $value instanceof self) ? $value->toArray() : $value; + }, $this->items); + } + + public function all() + { + return $this->items; + } + + /** + * 合并数组 + * + * @access public + * @param mixed $items + * @return static + */ + public function merge($items) + { + return new static(array_merge($this->items, $this->convertToArray($items))); + } + + /** + * 交换数组中的键和值 + * + * @access public + * @return static + */ + public function flip() + { + return new static(array_flip($this->items)); + } + + /** + * 按指定键整理数据 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 键名 + * @return array + */ + public function dictionary($items = null, &$indexKey = null) + { + if ($items instanceof self || $items instanceof Paginator) { + $items = $items->all(); + } + + $items = is_null($items) ? $this->items : $items; + + if ($items && empty($indexKey)) { + $indexKey = is_array($items[0]) ? 'id' : $items[0]->getPk(); + } + + if (isset($indexKey) && is_string($indexKey)) { + return array_column($items, null, $indexKey); + } + + return $items; + } + + /** + * 比较数组,返回差集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function diff($items, $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $diff = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (!isset($dictionary[$item[$indexKey]])) { + $diff[] = $item; + } + } + } + + return new static($diff); + } + + /** + * 比较数组,返回交集 + * + * @access public + * @param mixed $items 数据 + * @param string $indexKey 指定比较的键名 + * @return static + */ + public function intersect($items, $indexKey = null) + { + if ($this->isEmpty() || is_scalar($this->items[0])) { + return new static(array_diff($this->items, $this->convertToArray($items))); + } + + $intersect = []; + $dictionary = $this->dictionary($items, $indexKey); + + if (is_string($indexKey)) { + foreach ($this->items as $item) { + if (isset($dictionary[$item[$indexKey]])) { + $intersect[] = $item; + } + } + } + + return new static($intersect); + } + + /** + * 返回数组中所有的键名 + * + * @access public + * @return array + */ + public function keys() + { + $current = current($this->items); + + if (is_scalar($current)) { + $array = $this->items; + } elseif (is_array($current)) { + $array = $current; + } else { + $array = $current->toArray(); + } + + return array_keys($array); + } + + /** + * 删除数组的最后一个元素(出栈) + * + * @access public + * @return mixed + */ + public function pop() + { + return array_pop($this->items); + } + + /** + * 通过使用用户自定义函数,以字符串返回数组 + * + * @access public + * @param callable $callback + * @param mixed $initial + * @return mixed + */ + public function reduce(callable $callback, $initial = null) + { + return array_reduce($this->items, $callback, $initial); + } + + /** + * 以相反的顺序返回数组。 + * + * @access public + * @return static + */ + public function reverse() + { + return new static(array_reverse($this->items)); + } + + /** + * 删除数组中首个元素,并返回被删除元素的值 + * + * @access public + * @return mixed + */ + public function shift() + { + return array_shift($this->items); + } + + /** + * 在数组结尾插入一个元素 + * @access public + * @param mixed $value + * @param mixed $key + * @return void + */ + public function push($value, $key = null) + { + if (is_null($key)) { + $this->items[] = $value; + } else { + $this->items[$key] = $value; + } + } + + /** + * 把一个数组分割为新的数组块. + * + * @access public + * @param int $size + * @param bool $preserveKeys + * @return static + */ + public function chunk($size, $preserveKeys = false) + { + $chunks = []; + + foreach (array_chunk($this->items, $size, $preserveKeys) as $chunk) { + $chunks[] = new static($chunk); + } + + return new static($chunks); + } + + /** + * 在数组开头插入一个元素 + * @access public + * @param mixed $value + * @param mixed $key + * @return void + */ + public function unshift($value, $key = null) + { + if (is_null($key)) { + array_unshift($this->items, $value); + } else { + $this->items = [$key => $value] + $this->items; + } + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * 用回调函数处理数组中的元素 + * @access public + * @param callable|null $callback + * @return static + */ + public function map(callable $callback) + { + return new static(array_map($callback, $this->items)); + } + + /** + * 用回调函数过滤数组中的元素 + * @access public + * @param callable|null $callback + * @return static + */ + public function filter(callable $callback = null) + { + if ($callback) { + return new static(array_filter($this->items, $callback)); + } + + return new static(array_filter($this->items)); + } + + /** + * 根据字段条件过滤数组中的元素 + * @access public + * @param string $field 字段名 + * @param mixed $operator 操作符 + * @param mixed $value 数据 + * @return static + */ + public function where($field, $operator, $value = null) + { + if (is_null($value)) { + $value = $operator; + $operator = '='; + } + + return $this->filter(function ($data) use ($field, $operator, $value) { + if (strpos($field, '.')) { + list($field, $relation) = explode('.', $field); + + $result = isset($data[$field][$relation]) ? $data[$field][$relation] : null; + } else { + $result = isset($data[$field]) ? $data[$field] : null; + } + + switch ($operator) { + case '===': + return $result === $value; + case '!==': + return $result !== $value; + case '!=': + case '<>': + return $result != $value; + case '>': + return $result > $value; + case '>=': + return $result >= $value; + case '<': + return $result < $value; + case '<=': + return $result <= $value; + case 'like': + return is_string($result) && false !== strpos($result, $value); + case 'not like': + return is_string($result) && false === strpos($result, $value); + case 'in': + return is_scalar($result) && in_array($result, $value, true); + case 'not in': + return is_scalar($result) && !in_array($result, $value, true); + case 'between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result >= $min && $result <= $max; + case 'not between': + list($min, $max) = is_string($value) ? explode(',', $value) : $value; + return is_scalar($result) && $result > $max || $result < $min; + case '==': + case '=': + default: + return $result == $value; + } + }); + } + + /** + * 返回数据中指定的一列 + * @access public + * @param mixed $columnKey 键名 + * @param mixed $indexKey 作为索引值的列 + * @return array + */ + public function column($columnKey, $indexKey = null) + { + return array_column($this->items, $columnKey, $indexKey); + } + + /** + * 对数组排序 + * + * @access public + * @param callable|null $callback + * @return static + */ + public function sort(callable $callback = null) + { + $items = $this->items; + + $callback = $callback ?: function ($a, $b) { + return $a == $b ? 0 : (($a < $b) ? -1 : 1); + + }; + + uasort($items, $callback); + + return new static($items); + } + + /** + * 指定字段排序 + * @access public + * @param string $field 排序字段 + * @param string $order 排序 + * @param bool $intSort 是否为数字排序 + * @return $this + */ + public function order($field, $order = null, $intSort = true) + { + return $this->sort(function ($a, $b) use ($field, $order, $intSort) { + $fieldA = isset($a[$field]) ? $a[$field] : null; + $fieldB = isset($b[$field]) ? $b[$field] : null; + + if ($intSort) { + return 'desc' == strtolower($order) ? $fieldB >= $fieldA : $fieldA >= $fieldB; + } else { + return 'desc' == strtolower($order) ? strcmp($fieldB, $fieldA) : strcmp($fieldA, $fieldB); + } + }); + } + + /** + * 将数组打乱 + * + * @access public + * @return static + */ + public function shuffle() + { + $items = $this->items; + + shuffle($items); + + return new static($items); + } + + /** + * 截取数组 + * + * @access public + * @param int $offset + * @param int $length + * @param bool $preserveKeys + * @return static + */ + public function slice($offset, $length = null, $preserveKeys = false) + { + return new static(array_slice($this->items, $offset, $length, $preserveKeys)); + } + + // ArrayAccess + public function offsetExists($offset) + { + return array_key_exists($offset, $this->items); + } + + public function offsetGet($offset) + { + return $this->items[$offset]; + } + + public function offsetSet($offset, $value) + { + if (is_null($offset)) { + $this->items[] = $value; + } else { + $this->items[$offset] = $value; + } + } + + public function offsetUnset($offset) + { + unset($this->items[$offset]); + } + + //Countable + public function count() + { + return count($this->items); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->items); + } + + //JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换当前数据集为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + public function __toString() + { + return $this->toJson(); + } + + /** + * 转换成数组 + * + * @access public + * @param mixed $items + * @return array + */ + protected function convertToArray($items) + { + if ($items instanceof self) { + return $items->all(); + } + + return (array) $items; + } +} diff --git a/thinkphp/library/think/Config.php b/thinkphp/library/think/Config.php new file mode 100644 index 0000000..bec6222 --- /dev/null +++ b/thinkphp/library/think/Config.php @@ -0,0 +1,398 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use Yaconf; + +class Config implements \ArrayAccess +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 配置前缀 + * @var string + */ + protected $prefix = 'app'; + + /** + * 配置文件目录 + * @var string + */ + protected $path; + + /** + * 配置文件后缀 + * @var string + */ + protected $ext; + + /** + * 是否支持Yaconf + * @var bool + */ + protected $yaconf; + + /** + * 构造方法 + * @access public + */ + public function __construct($path = '', $ext = '.php') + { + $this->path = $path; + $this->ext = $ext; + $this->yaconf = class_exists('Yaconf'); + } + + public static function __make(App $app) + { + $path = $app->getConfigPath(); + $ext = $app->getConfigExt(); + return new static($path, $ext); + } + + /** + * 设置开启Yaconf + * @access public + * @param bool|string $yaconf 是否使用Yaconf + * @return void + */ + public function setYaconf($yaconf) + { + if ($this->yaconf) { + $this->yaconf = $yaconf; + } + } + + /** + * 设置配置参数默认前缀 + * @access public + * @param string $prefix 前缀 + * @return void + */ + public function setDefaultPrefix($prefix) + { + $this->prefix = $prefix; + } + + /** + * 解析配置文件或内容 + * @access public + * @param string $config 配置文件路径或内容 + * @param string $type 配置解析类型 + * @param string $name 配置名(如设置即表示二级配置) + * @return mixed + */ + public function parse($config, $type = '', $name = '') + { + if (empty($type)) { + $type = pathinfo($config, PATHINFO_EXTENSION); + } + + $object = Loader::factory($type, '\\think\\config\\driver\\', $config); + + return $this->set($object->parse(), $name); + } + + /** + * 加载配置文件(多种格式) + * @access public + * @param string $file 配置文件名 + * @param string $name 一级配置名 + * @return mixed + */ + public function load($file, $name = '') + { + if (is_file($file)) { + $filename = $file; + } elseif (is_file($this->path . $file . $this->ext)) { + $filename = $this->path . $file . $this->ext; + } + + if (isset($filename)) { + return $this->loadFile($filename, $name); + } elseif ($this->yaconf && Yaconf::has($file)) { + return $this->set(Yaconf::get($file), $name); + } + + return $this->config; + } + + /** + * 获取实际的yaconf配置参数 + * @access protected + * @param string $name 配置参数名 + * @return string + */ + protected function getYaconfName($name) + { + if ($this->yaconf && is_string($this->yaconf)) { + return $this->yaconf . '.' . $name; + } + + return $name; + } + + /** + * 获取yaconf配置 + * @access public + * @param string $name 配置参数名 + * @param mixed $default 默认值 + * @return mixed + */ + public function yaconf($name, $default = null) + { + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + return Yaconf::get($yaconfName); + } + } + + return $default; + } + + protected function loadFile($file, $name) + { + $name = strtolower($name); + $type = pathinfo($file, PATHINFO_EXTENSION); + + if ('php' == $type) { + return $this->set(include $file, $name); + } elseif ('yaml' == $type && function_exists('yaml_parse_file')) { + return $this->set(yaml_parse_file($file), $name); + } + + return $this->parse($file, $type, $name); + } + + /** + * 检测配置是否存在 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @return bool + */ + public function has($name) + { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + return !is_null($this->get($name)); + } + + /** + * 获取一级配置 + * @access public + * @param string $name 一级配置名 + * @return array + */ + public function pull($name) + { + $name = strtolower($name); + + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + $config = Yaconf::get($yaconfName); + return isset($this->config[$name]) ? array_merge($this->config[$name], $config) : $config; + } + } + + return isset($this->config[$name]) ? $this->config[$name] : []; + } + + /** + * 获取配置参数 为空则获取所有配置 + * @access public + * @param string $name 配置参数名(支持多级配置 .号分割) + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name = null, $default = null) + { + if ($name && false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + // 无参数时获取所有 + if (empty($name)) { + return $this->config; + } + + if ('.' == substr($name, -1)) { + return $this->pull(substr($name, 0, -1)); + } + + if ($this->yaconf) { + $yaconfName = $this->getYaconfName($name); + + if (Yaconf::has($yaconfName)) { + return Yaconf::get($yaconfName); + } + } + + $name = explode('.', $name); + $name[0] = strtolower($name[0]); + $config = $this->config; + + // 按.拆分成多维数组进行判断 + foreach ($name as $val) { + if (isset($config[$val])) { + $config = $config[$val]; + } else { + return $default; + } + } + + return $config; + } + + /** + * 设置配置参数 name为数组则为批量设置 + * @access public + * @param string|array $name 配置参数名(支持三级配置 .号分割) + * @param mixed $value 配置值 + * @return mixed + */ + public function set($name, $value = null) + { + if (is_string($name)) { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + $this->config[strtolower($name[0])][$name[1]] = $value; + } else { + $this->config[strtolower($name[0])][$name[1]][$name[2]] = $value; + } + + return $value; + } elseif (is_array($name)) { + // 批量设置 + if (!empty($value)) { + if (isset($this->config[$value])) { + $result = array_merge($this->config[$value], $name); + } else { + $result = $name; + } + + $this->config[$value] = $result; + } else { + $result = $this->config = array_merge($this->config, $name); + } + } else { + // 为空直接返回 已有配置 + $result = $this->config; + } + + return $result; + } + + /** + * 移除配置 + * @access public + * @param string $name 配置参数名(支持三级配置 .号分割) + * @return void + */ + public function remove($name) + { + if (false === strpos($name, '.')) { + $name = $this->prefix . '.' . $name; + } + + $name = explode('.', $name, 3); + + if (count($name) == 2) { + unset($this->config[strtolower($name[0])][$name[1]]); + } else { + unset($this->config[strtolower($name[0])][$name[1]][$name[2]]); + } + } + + /** + * 重置配置参数 + * @access public + * @param string $prefix 配置前缀名 + * @return void + */ + public function reset($prefix = '') + { + if ('' === $prefix) { + $this->config = []; + } else { + $this->config[$prefix] = []; + } + } + + /** + * 设置配置 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->set($name, $value); + } + + /** + * 获取配置参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get($name) + { + return $this->get($name); + } + + /** + * 检测是否存在参数 + * @access public + * @param string $name 参数名 + * @return bool + */ + public function __isset($name) + { + return $this->has($name); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->set($name, $value); + } + + public function offsetExists($name) + { + return $this->has($name); + } + + public function offsetUnset($name) + { + $this->remove($name); + } + + public function offsetGet($name) + { + return $this->get($name); + } +} diff --git a/thinkphp/library/think/Console.php b/thinkphp/library/think/Console.php new file mode 100644 index 0000000..d121290 --- /dev/null +++ b/thinkphp/library/think/Console.php @@ -0,0 +1,824 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Command; +use think\console\command\Help as HelpCommand; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\driver\Buffer; + +class Console +{ + + private $name; + private $version; + + /** @var Command[] */ + private $commands = []; + + private $wantHelps = false; + + private $catchExceptions = true; + private $autoExit = true; + private $definition; + private $defaultCommand; + + private static $defaultCommands = [ + 'help' => "think\\console\\command\\Help", + 'list' => "think\\console\\command\\Lists", + 'build' => "think\\console\\command\\Build", + 'clear' => "think\\console\\command\\Clear", + 'make:command' => "think\\console\\command\\make\\Command", + 'make:controller' => "think\\console\\command\\make\\Controller", + 'make:model' => "think\\console\\command\\make\\Model", + 'make:middleware' => "think\\console\\command\\make\\Middleware", + 'make:validate' => "think\\console\\command\\make\\Validate", + 'optimize:autoload' => "think\\console\\command\\optimize\\Autoload", + 'optimize:config' => "think\\console\\command\\optimize\\Config", + 'optimize:schema' => "think\\console\\command\\optimize\\Schema", + 'optimize:route' => "think\\console\\command\\optimize\\Route", + 'run' => "think\\console\\command\\RunServer", + 'version' => "think\\console\\command\\Version", + 'route:list' => "think\\console\\command\\RouteList", + ]; + + /** + * Console constructor. + * @access public + * @param string $name 名称 + * @param string $version 版本 + * @param null|string $user 执行用户 + */ + public function __construct($name = 'UNKNOWN', $version = 'UNKNOWN', $user = null) + { + $this->name = $name; + $this->version = $version; + + if ($user) { + $this->setUser($user); + } + + $this->defaultCommand = 'list'; + $this->definition = $this->getDefaultInputDefinition(); + } + + /** + * 设置执行用户 + * @param $user + */ + public function setUser($user) + { + if (DIRECTORY_SEPARATOR == '\\') { + return; + } + + $user = posix_getpwnam($user); + if ($user) { + posix_setuid($user['uid']); + posix_setgid($user['gid']); + } + } + + /** + * 初始化 Console + * @access public + * @param bool $run 是否运行 Console + * @return int|Console + */ + public static function init($run = true) + { + static $console; + + if (!$console) { + $config = Container::get('config')->pull('console'); + $console = new self($config['name'], $config['version'], $config['user']); + + $commands = $console->getDefinedCommands($config); + + // 添加指令集 + $console->addCommands($commands); + } + + if ($run) { + // 运行 + return $console->run(); + } else { + return $console; + } + } + + /** + * @access public + * @param array $config + * @return array + */ + public function getDefinedCommands(array $config = []) + { + $commands = self::$defaultCommands; + + if (!empty($config['auto_path']) && is_dir($config['auto_path'])) { + // 自动加载指令类 + $files = scandir($config['auto_path']); + + if (count($files) > 2) { + $beforeClass = get_declared_classes(); + + foreach ($files as $file) { + if (pathinfo($file, PATHINFO_EXTENSION) == 'php') { + include $config['auto_path'] . $file; + } + } + + $afterClass = get_declared_classes(); + $commands = array_merge($commands, array_diff($afterClass, $beforeClass)); + } + } + + $file = Container::get('env')->get('app_path') . 'command.php'; + + if (is_file($file)) { + $appCommands = include $file; + + if (is_array($appCommands)) { + $commands = array_merge($commands, $appCommands); + } + } + + return $commands; + } + + /** + * @access public + * @param string $command + * @param array $parameters + * @param string $driver + * @return Output|Buffer + */ + public static function call($command, array $parameters = [], $driver = 'buffer') + { + $console = self::init(false); + + array_unshift($parameters, $command); + + $input = new Input($parameters); + $output = new Output($driver); + + $console->setCatchExceptions(false); + $console->find($command)->run($input, $output); + + return $output; + } + + /** + * 执行当前的指令 + * @access public + * @return int + * @throws \Exception + * @api + */ + public function run() + { + $input = new Input(); + $output = new Output(); + + $this->configureIO($input, $output); + + try { + $exitCode = $this->doRun($input, $output); + } catch (\Exception $e) { + if (!$this->catchExceptions) { + throw $e; + } + + $output->renderException($e); + + $exitCode = $e->getCode(); + if (is_numeric($exitCode)) { + $exitCode = (int) $exitCode; + if (0 === $exitCode) { + $exitCode = 1; + } + } else { + $exitCode = 1; + } + } + + if ($this->autoExit) { + if ($exitCode > 255) { + $exitCode = 255; + } + + exit($exitCode); + } + + return $exitCode; + } + + /** + * 执行指令 + * @access public + * @param Input $input + * @param Output $output + * @return int + */ + public function doRun(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--version', '-V'])) { + $output->writeln($this->getLongVersion()); + + return 0; + } + + $name = $this->getCommandName($input); + + if (true === $input->hasParameterOption(['--help', '-h'])) { + if (!$name) { + $name = 'help'; + $input = new Input(['help']); + } else { + $this->wantHelps = true; + } + } + + if (!$name) { + $name = $this->defaultCommand; + $input = new Input([$this->defaultCommand]); + } + + $command = $this->find($name); + + $exitCode = $this->doRunCommand($command, $input, $output); + + return $exitCode; + } + + /** + * 设置输入参数定义 + * @access public + * @param InputDefinition $definition + */ + public function setDefinition(InputDefinition $definition) + { + $this->definition = $definition; + } + + /** + * 获取输入参数定义 + * @access public + * @return InputDefinition The InputDefinition instance + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * Gets the help message. + * @access public + * @return string A help message. + */ + public function getHelp() + { + return $this->getLongVersion(); + } + + /** + * 是否捕获异常 + * @access public + * @param bool $boolean + * @api + */ + public function setCatchExceptions($boolean) + { + $this->catchExceptions = (bool) $boolean; + } + + /** + * 是否自动退出 + * @access public + * @param bool $boolean + * @api + */ + public function setAutoExit($boolean) + { + $this->autoExit = (bool) $boolean; + } + + /** + * 获取名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置名称 + * @access public + * @param string $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * 获取版本 + * @access public + * @return string + * @api + */ + public function getVersion() + { + return $this->version; + } + + /** + * 设置版本 + * @access public + * @param string $version + */ + public function setVersion($version) + { + $this->version = $version; + } + + /** + * 获取完整的版本号 + * @access public + * @return string + */ + public function getLongVersion() + { + if ('UNKNOWN' !== $this->getName() && 'UNKNOWN' !== $this->getVersion()) { + return sprintf('%s version %s', $this->getName(), $this->getVersion()); + } + + return 'Console Tool'; + } + + /** + * 注册一个指令 (便于动态创建指令) + * @access public + * @param string $name 指令名 + * @return Command + */ + public function register($name) + { + return $this->add(new Command($name)); + } + + /** + * 添加指令集 + * @access public + * @param array $commands + */ + public function addCommands(array $commands) + { + foreach ($commands as $key => $command) { + if (is_subclass_of($command, "\\think\\console\\Command")) { + // 注册指令 + $this->add($command, is_numeric($key) ? '' : $key); + } + } + } + + /** + * 注册一个指令(对象) + * @access public + * @param mixed $command 指令对象或者指令类名 + * @param string $name 指令名 留空则自动获取 + * @return mixed + */ + public function add($command, $name) + { + if ($name) { + $this->commands[$name] = $command; + return; + } + + if (is_string($command)) { + $command = new $command(); + } + + $command->setConsole($this); + + if (!$command->isEnabled()) { + $command->setConsole(null); + return; + } + + if (null === $command->getDefinition()) { + throw new \LogicException(sprintf('Command class "%s" is not correctly initialized. You probably forgot to call the parent constructor.', get_class($command))); + } + + $this->commands[$command->getName()] = $command; + + foreach ($command->getAliases() as $alias) { + $this->commands[$alias] = $command; + } + + return $command; + } + + /** + * 获取指令 + * @access public + * @param string $name 指令名称 + * @return Command + * @throws \InvalidArgumentException + */ + public function get($name) + { + if (!isset($this->commands[$name])) { + throw new \InvalidArgumentException(sprintf('The command "%s" does not exist.', $name)); + } + + $command = $this->commands[$name]; + + if (is_string($command)) { + $command = new $command(); + } + + $command->setConsole($this); + + if ($this->wantHelps) { + $this->wantHelps = false; + + /** @var HelpCommand $helpCommand */ + $helpCommand = $this->get('help'); + $helpCommand->setCommand($command); + + return $helpCommand; + } + + return $command; + } + + /** + * 某个指令是否存在 + * @access public + * @param string $name 指令名称 + * @return bool + */ + public function has($name) + { + return isset($this->commands[$name]); + } + + /** + * 获取所有的命名空间 + * @access public + * @return array + */ + public function getNamespaces() + { + $namespaces = []; + foreach ($this->commands as $command) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($command->getName())); + + foreach ($command->getAliases() as $alias) { + $namespaces = array_merge($namespaces, $this->extractAllNamespaces($alias)); + } + } + + return array_values(array_unique(array_filter($namespaces))); + } + + /** + * 查找注册命名空间中的名称或缩写。 + * @access public + * @param string $namespace + * @return string + * @throws \InvalidArgumentException + */ + public function findNamespace($namespace) + { + $allNamespaces = $this->getNamespaces(); + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $namespace); + $namespaces = preg_grep('{^' . $expr . '}', $allNamespaces); + + if (empty($namespaces)) { + $message = sprintf('There are no commands defined in the "%s" namespace.', $namespace); + + if ($alternatives = $this->findAlternatives($namespace, $allNamespaces)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($namespace, $namespaces, true); + if (count($namespaces) > 1 && !$exact) { + throw new \InvalidArgumentException(sprintf('The namespace "%s" is ambiguous (%s).', $namespace, $this->getAbbreviationSuggestions(array_values($namespaces)))); + } + + return $exact ? $namespace : reset($namespaces); + } + + /** + * 查找指令 + * @access public + * @param string $name 名称或者别名 + * @return Command + * @throws \InvalidArgumentException + */ + public function find($name) + { + $allCommands = array_keys($this->commands); + + $expr = preg_replace_callback('{([^:]+|)}', function ($matches) { + return preg_quote($matches[1]) . '[^:]*'; + }, $name); + + $commands = preg_grep('{^' . $expr . '}', $allCommands); + + if (empty($commands) || count(preg_grep('{^' . $expr . '$}', $commands)) < 1) { + if (false !== $pos = strrpos($name, ':')) { + $this->findNamespace(substr($name, 0, $pos)); + } + + $message = sprintf('Command "%s" is not defined.', $name); + + if ($alternatives = $this->findAlternatives($name, $allCommands)) { + if (1 == count($alternatives)) { + $message .= "\n\nDid you mean this?\n "; + } else { + $message .= "\n\nDid you mean one of these?\n "; + } + $message .= implode("\n ", $alternatives); + } + + throw new \InvalidArgumentException($message); + } + + $exact = in_array($name, $commands, true); + if (count($commands) > 1 && !$exact) { + $suggestions = $this->getAbbreviationSuggestions(array_values($commands)); + + throw new \InvalidArgumentException(sprintf('Command "%s" is ambiguous (%s).', $name, $suggestions)); + } + + return $this->get($exact ? $name : reset($commands)); + } + + /** + * 获取所有的指令 + * @access public + * @param string $namespace 命名空间 + * @return Command[] + * @api + */ + public function all($namespace = null) + { + if (null === $namespace) { + return $this->commands; + } + + $commands = []; + foreach ($this->commands as $name => $command) { + if ($this->extractNamespace($name, substr_count($namespace, ':') + 1) === $namespace) { + $commands[$name] = $command; + } + } + + return $commands; + } + + /** + * 获取可能的指令名 + * @access public + * @param array $names + * @return array + */ + public static function getAbbreviations($names) + { + $abbrevs = []; + foreach ($names as $name) { + for ($len = strlen($name); $len > 0; --$len) { + $abbrev = substr($name, 0, $len); + $abbrevs[$abbrev][] = $name; + } + } + + return $abbrevs; + } + + /** + * 配置基于用户的参数和选项的输入和输出实例。 + * @access protected + * @param Input $input 输入实例 + * @param Output $output 输出实例 + */ + protected function configureIO(Input $input, Output $output) + { + if (true === $input->hasParameterOption(['--ansi'])) { + $output->setDecorated(true); + } elseif (true === $input->hasParameterOption(['--no-ansi'])) { + $output->setDecorated(false); + } + + if (true === $input->hasParameterOption(['--no-interaction', '-n'])) { + $input->setInteractive(false); + } + + if (true === $input->hasParameterOption(['--quiet', '-q'])) { + $output->setVerbosity(Output::VERBOSITY_QUIET); + } else { + if ($input->hasParameterOption('-vvv') || $input->hasParameterOption('--verbose=3') || $input->getParameterOption('--verbose') === 3) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } elseif ($input->hasParameterOption('-vv') || $input->hasParameterOption('--verbose=2') || $input->getParameterOption('--verbose') === 2) { + $output->setVerbosity(Output::VERBOSITY_VERY_VERBOSE); + } elseif ($input->hasParameterOption('-v') || $input->hasParameterOption('--verbose=1') || $input->hasParameterOption('--verbose') || $input->getParameterOption('--verbose')) { + $output->setVerbosity(Output::VERBOSITY_VERBOSE); + } + } + } + + /** + * 执行指令 + * @access protected + * @param Command $command 指令实例 + * @param Input $input 输入实例 + * @param Output $output 输出实例 + * @return int + * @throws \Exception + */ + protected function doRunCommand(Command $command, Input $input, Output $output) + { + return $command->run($input, $output); + } + + /** + * 获取指令的基础名称 + * @access protected + * @param Input $input + * @return string + */ + protected function getCommandName(Input $input) + { + return $input->getFirstArgument(); + } + + /** + * 获取默认输入定义 + * @access protected + * @return InputDefinition + */ + protected function getDefaultInputDefinition() + { + return new InputDefinition([ + new InputArgument('command', InputArgument::REQUIRED, 'The command to execute'), + new InputOption('--help', '-h', InputOption::VALUE_NONE, 'Display this help message'), + new InputOption('--version', '-V', InputOption::VALUE_NONE, 'Display this console version'), + new InputOption('--quiet', '-q', InputOption::VALUE_NONE, 'Do not output any message'), + new InputOption('--verbose', '-v|vv|vvv', InputOption::VALUE_NONE, 'Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug'), + new InputOption('--ansi', '', InputOption::VALUE_NONE, 'Force ANSI output'), + new InputOption('--no-ansi', '', InputOption::VALUE_NONE, 'Disable ANSI output'), + new InputOption('--no-interaction', '-n', InputOption::VALUE_NONE, 'Do not ask any interactive question'), + ]); + } + + public static function addDefaultCommands(array $classnames) + { + self::$defaultCommands = array_merge(self::$defaultCommands, $classnames); + } + + /** + * 获取可能的建议 + * @access private + * @param array $abbrevs + * @return string + */ + private function getAbbreviationSuggestions($abbrevs) + { + return sprintf('%s, %s%s', $abbrevs[0], $abbrevs[1], count($abbrevs) > 2 ? sprintf(' and %d more', count($abbrevs) - 2) : ''); + } + + /** + * 返回命名空间部分 + * @access public + * @param string $name 指令 + * @param string $limit 部分的命名空间的最大数量 + * @return string + */ + public function extractNamespace($name, $limit = null) + { + $parts = explode(':', $name); + array_pop($parts); + + return implode(':', null === $limit ? $parts : array_slice($parts, 0, $limit)); + } + + /** + * 查找可替代的建议 + * @access private + * @param string $name + * @param array|\Traversable $collection + * @return array + */ + private function findAlternatives($name, $collection) + { + $threshold = 1e3; + $alternatives = []; + + $collectionParts = []; + foreach ($collection as $item) { + $collectionParts[$item] = explode(':', $item); + } + + foreach (explode(':', $name) as $i => $subname) { + foreach ($collectionParts as $collectionName => $parts) { + $exists = isset($alternatives[$collectionName]); + if (!isset($parts[$i]) && $exists) { + $alternatives[$collectionName] += $threshold; + continue; + } elseif (!isset($parts[$i])) { + continue; + } + + $lev = levenshtein($subname, $parts[$i]); + if ($lev <= strlen($subname) / 3 || '' !== $subname && false !== strpos($parts[$i], $subname)) { + $alternatives[$collectionName] = $exists ? $alternatives[$collectionName] + $lev : $lev; + } elseif ($exists) { + $alternatives[$collectionName] += $threshold; + } + } + } + + foreach ($collection as $item) { + $lev = levenshtein($name, $item); + if ($lev <= strlen($name) / 3 || false !== strpos($item, $name)) { + $alternatives[$item] = isset($alternatives[$item]) ? $alternatives[$item] - $lev : $lev; + } + } + + $alternatives = array_filter($alternatives, function ($lev) use ($threshold) { + return $lev < 2 * $threshold; + }); + asort($alternatives); + + return array_keys($alternatives); + } + + /** + * 设置默认的指令 + * @access public + * @param string $commandName The Command name + */ + public function setDefaultCommand($commandName) + { + $this->defaultCommand = $commandName; + } + + /** + * 返回所有的命名空间 + * @access private + * @param string $name + * @return array + */ + private function extractAllNamespaces($name) + { + $parts = explode(':', $name, -1); + $namespaces = []; + + foreach ($parts as $part) { + if (count($namespaces)) { + $namespaces[] = end($namespaces) . ':' . $part; + } else { + $namespaces[] = $part; + } + } + + return $namespaces; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['commands'], $data['definition']); + + return $data; + } +} diff --git a/thinkphp/library/think/Container.php b/thinkphp/library/think/Container.php new file mode 100644 index 0000000..cd7954c --- /dev/null +++ b/thinkphp/library/think/Container.php @@ -0,0 +1,568 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Closure; +use Countable; +use InvalidArgumentException; +use IteratorAggregate; +use ReflectionClass; +use ReflectionException; +use ReflectionFunction; +use ReflectionMethod; +use think\exception\ClassNotFoundException; + +/** + * @package think + * @property Build $build + * @property Cache $cache + * @property Config $config + * @property Cookie $cookie + * @property Debug $debug + * @property Env $env + * @property Hook $hook + * @property Lang $lang + * @property Middleware $middleware + * @property Request $request + * @property Response $response + * @property Route $route + * @property Session $session + * @property Template $template + * @property Url $url + * @property Validate $validate + * @property View $view + * @property route\RuleName $rule_name + * @property Log $log + */ +class Container implements ArrayAccess, IteratorAggregate, Countable +{ + /** + * 容器对象实例 + * @var Container + */ + protected static $instance; + + /** + * 容器中的对象实例 + * @var array + */ + protected $instances = []; + + /** + * 容器绑定标识 + * @var array + */ + protected $bind = [ + 'app' => App::class, + 'build' => Build::class, + 'cache' => Cache::class, + 'config' => Config::class, + 'cookie' => Cookie::class, + 'debug' => Debug::class, + 'env' => Env::class, + 'hook' => Hook::class, + 'lang' => Lang::class, + 'log' => Log::class, + 'middleware' => Middleware::class, + 'request' => Request::class, + 'response' => Response::class, + 'route' => Route::class, + 'session' => Session::class, + 'template' => Template::class, + 'url' => Url::class, + 'validate' => Validate::class, + 'view' => View::class, + 'rule_name' => route\RuleName::class, + // 接口依赖注入 + 'think\LoggerInterface' => Log::class, + ]; + + /** + * 容器标识别名 + * @var array + */ + protected $name = []; + + /** + * 获取当前容器的实例(单例) + * @access public + * @return static + */ + public static function getInstance() + { + if (is_null(static::$instance)) { + static::$instance = new static; + } + + return static::$instance; + } + + /** + * 设置当前容器的实例 + * @access public + * @param object $instance + * @return void + */ + public static function setInstance($instance) + { + static::$instance = $instance; + } + + /** + * 获取容器中的对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public static function get($abstract, $vars = [], $newInstance = false) + { + return static::getInstance()->make($abstract, $vars, $newInstance); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return Container + */ + public static function set($abstract, $concrete = null) + { + return static::getInstance()->bindTo($abstract, $concrete); + } + + /** + * 移除容器中的对象实例 + * @access public + * @param string $abstract 类标识、接口 + * @return void + */ + public static function remove($abstract) + { + return static::getInstance()->delete($abstract); + } + + /** + * 清除容器中的对象实例 + * @access public + * @return void + */ + public static function clear() + { + return static::getInstance()->flush(); + } + + /** + * 绑定一个类、闭包、实例、接口实现到容器 + * @access public + * @param string|array $abstract 类标识、接口 + * @param mixed $concrete 要绑定的类、闭包或者实例 + * @return $this + */ + public function bindTo($abstract, $concrete = null) + { + if (is_array($abstract)) { + $this->bind = array_merge($this->bind, $abstract); + } elseif ($concrete instanceof Closure) { + $this->bind[$abstract] = $concrete; + } elseif (is_object($concrete)) { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + $this->instances[$abstract] = $concrete; + } else { + $this->bind[$abstract] = $concrete; + } + + return $this; + } + + /** + * 绑定一个类实例当容器 + * @access public + * @param string $abstract 类名或者标识 + * @param object|\Closure $instance 类的实例 + * @return $this + */ + public function instance($abstract, $instance) + { + if ($instance instanceof \Closure) { + $this->bind[$abstract] = $instance; + } else { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + + $this->instances[$abstract] = $instance; + } + + return $this; + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function bound($abstract) + { + return isset($this->bind[$abstract]) || isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在对象实例 + * @access public + * @param string $abstract 类名或者标识 + * @return bool + */ + public function exists($abstract) + { + if (isset($this->bind[$abstract])) { + $abstract = $this->bind[$abstract]; + } + + return isset($this->instances[$abstract]); + } + + /** + * 判断容器中是否存在类及标识 + * @access public + * @param string $name 类名或者标识 + * @return bool + */ + public function has($name) + { + return $this->bound($name); + } + + /** + * 创建类的实例 + * @access public + * @param string $abstract 类名或者标识 + * @param array|true $vars 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + public function make($abstract, $vars = [], $newInstance = false) + { + if (true === $vars) { + // 总是创建新的实例化对象 + $newInstance = true; + $vars = []; + } + + $abstract = isset($this->name[$abstract]) ? $this->name[$abstract] : $abstract; + + if (isset($this->instances[$abstract]) && !$newInstance) { + return $this->instances[$abstract]; + } + + if (isset($this->bind[$abstract])) { + $concrete = $this->bind[$abstract]; + + if ($concrete instanceof Closure) { + $object = $this->invokeFunction($concrete, $vars); + } else { + $this->name[$abstract] = $concrete; + return $this->make($concrete, $vars, $newInstance); + } + } else { + $object = $this->invokeClass($abstract, $vars); + } + + if (!$newInstance) { + $this->instances[$abstract] = $object; + } + + return $object; + } + + /** + * 删除容器中的对象实例 + * @access public + * @param string|array $abstract 类名或者标识 + * @return void + */ + public function delete($abstract) + { + foreach ((array) $abstract as $name) { + $name = isset($this->name[$name]) ? $this->name[$name] : $name; + + if (isset($this->instances[$name])) { + unset($this->instances[$name]); + } + } + } + + /** + * 获取容器中的对象实例 + * @access public + * @return array + */ + public function all() + { + return $this->instances; + } + + /** + * 清除容器中的对象实例 + * @access public + * @return void + */ + public function flush() + { + $this->instances = []; + $this->bind = []; + $this->name = []; + } + + /** + * 执行函数或者闭包方法 支持参数调用 + * @access public + * @param mixed $function 函数或者闭包 + * @param array $vars 参数 + * @return mixed + */ + public function invokeFunction($function, $vars = []) + { + try { + $reflect = new ReflectionFunction($function); + + $args = $this->bindParams($reflect, $vars); + + return call_user_func_array($function, $args); + } catch (ReflectionException $e) { + throw new Exception('function not exists: ' . $function . '()'); + } + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param mixed $method 方法 + * @param array $vars 参数 + * @return mixed + */ + public function invokeMethod($method, $vars = []) + { + try { + if (is_array($method)) { + $class = is_object($method[0]) ? $method[0] : $this->invokeClass($method[0]); + $reflect = new ReflectionMethod($class, $method[1]); + } else { + // 静态方法 + $reflect = new ReflectionMethod($method); + } + + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs(isset($class) ? $class : null, $args); + } catch (ReflectionException $e) { + if (is_array($method) && is_object($method[0])) { + $method[0] = get_class($method[0]); + } + + throw new Exception('method not exists: ' . (is_array($method) ? $method[0] . '::' . $method[1] : $method) . '()'); + } + } + + /** + * 调用反射执行类的方法 支持参数绑定 + * @access public + * @param object $instance 对象实例 + * @param mixed $reflect 反射类 + * @param array $vars 参数 + * @return mixed + */ + public function invokeReflectMethod($instance, $reflect, $vars = []) + { + $args = $this->bindParams($reflect, $vars); + + return $reflect->invokeArgs($instance, $args); + } + + /** + * 调用反射执行callable 支持参数绑定 + * @access public + * @param mixed $callable + * @param array $vars 参数 + * @return mixed + */ + public function invoke($callable, $vars = []) + { + if ($callable instanceof Closure) { + return $this->invokeFunction($callable, $vars); + } + + return $this->invokeMethod($callable, $vars); + } + + /** + * 调用反射执行类的实例化 支持依赖注入 + * @access public + * @param string $class 类名 + * @param array $vars 参数 + * @return mixed + */ + public function invokeClass($class, $vars = []) + { + try { + $reflect = new ReflectionClass($class); + + if ($reflect->hasMethod('__make')) { + $method = new ReflectionMethod($class, '__make'); + + if ($method->isPublic() && $method->isStatic()) { + $args = $this->bindParams($method, $vars); + return $method->invokeArgs(null, $args); + } + } + + $constructor = $reflect->getConstructor(); + + $args = $constructor ? $this->bindParams($constructor, $vars) : []; + + return $reflect->newInstanceArgs($args); + + } catch (ReflectionException $e) { + throw new ClassNotFoundException('class not exists: ' . $class, $class); + } + } + + /** + * 绑定参数 + * @access protected + * @param \ReflectionMethod|\ReflectionFunction $reflect 反射类 + * @param array $vars 参数 + * @return array + */ + protected function bindParams($reflect, $vars = []) + { + if ($reflect->getNumberOfParameters() == 0) { + return []; + } + + // 判断数组类型 数字数组时按顺序绑定参数 + reset($vars); + $type = key($vars) === 0 ? 1 : 0; + $params = $reflect->getParameters(); + + foreach ($params as $param) { + $name = $param->getName(); + $lowerName = Loader::parseName($name); + $class = $param->getClass(); + + if ($class) { + $args[] = $this->getObjectParam($class->getName(), $vars); + } elseif (1 == $type && !empty($vars)) { + $args[] = array_shift($vars); + } elseif (0 == $type && isset($vars[$name])) { + $args[] = $vars[$name]; + } elseif (0 == $type && isset($vars[$lowerName])) { + $args[] = $vars[$lowerName]; + } elseif ($param->isDefaultValueAvailable()) { + $args[] = $param->getDefaultValue(); + } else { + throw new InvalidArgumentException('method param miss:' . $name); + } + } + + return $args; + } + + /** + * 获取对象类型的参数值 + * @access protected + * @param string $className 类名 + * @param array $vars 参数 + * @return mixed + */ + protected function getObjectParam($className, &$vars) + { + $array = $vars; + $value = array_shift($array); + + if ($value instanceof $className) { + $result = $value; + array_shift($vars); + } else { + $result = $this->make($className); + } + + return $result; + } + + public function __set($name, $value) + { + $this->bindTo($name, $value); + } + + public function __get($name) + { + return $this->make($name); + } + + public function __isset($name) + { + return $this->bound($name); + } + + public function __unset($name) + { + $this->delete($name); + } + + public function offsetExists($key) + { + return $this->__isset($key); + } + + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value) + { + $this->__set($key, $value); + } + + public function offsetUnset($key) + { + $this->__unset($key); + } + + //Countable + public function count() + { + return count($this->instances); + } + + //IteratorAggregate + public function getIterator() + { + return new ArrayIterator($this->instances); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['instances'], $data['instance']); + + return $data; + } +} diff --git a/thinkphp/library/think/Controller.php b/thinkphp/library/think/Controller.php new file mode 100644 index 0000000..a57da9e --- /dev/null +++ b/thinkphp/library/think/Controller.php @@ -0,0 +1,285 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ValidateException; +use traits\controller\Jump; + +class Controller +{ + use Jump; + + /** + * 视图类实例 + * @var \think\View + */ + protected $view; + + /** + * Request实例 + * @var \think\Request + */ + protected $request; + + /** + * 验证失败是否抛出异常 + * @var bool + */ + protected $failException = false; + + /** + * 是否批量验证 + * @var bool + */ + protected $batchValidate = false; + + /** + * 前置操作方法列表(即将废弃) + * @var array $beforeActionList + */ + protected $beforeActionList = []; + + /** + * 控制器中间件 + * @var array + */ + protected $middleware = []; + + /** + * 构造方法 + * @access public + */ + public function __construct(App $app = null) + { + $this->app = $app ?: Container::get('app'); + $this->request = $this->app['request']; + $this->view = $this->app['view']; + + // 控制器初始化 + $this->initialize(); + + // 前置操作方法 即将废弃 + foreach ((array) $this->beforeActionList as $method => $options) { + is_numeric($method) ? + $this->beforeAction($options) : + $this->beforeAction($method, $options); + } + } + + // 初始化 + protected function initialize() + {} + + // 注册控制器中间件 + public function registerMiddleware() + { + foreach ($this->middleware as $key => $val) { + if (!is_int($key)) { + $only = $except = null; + + if (isset($val['only'])) { + $only = array_map(function ($item) { + return strtolower($item); + }, $val['only']); + } elseif (isset($val['except'])) { + $except = array_map(function ($item) { + return strtolower($item); + }, $val['except']); + } + + if (isset($only) && !in_array($this->request->action(), $only)) { + continue; + } elseif (isset($except) && in_array($this->request->action(), $except)) { + continue; + } else { + $val = $key; + } + } + + $this->app['middleware']->controller($val); + } + } + + /** + * 前置操作 + * @access protected + * @param string $method 前置操作方法名 + * @param array $options 调用参数 ['only'=>[...]] 或者['except'=>[...]] + */ + protected function beforeAction($method, $options = []) + { + if (isset($options['only'])) { + if (is_string($options['only'])) { + $options['only'] = explode(',', $options['only']); + } + + $only = array_map(function ($val) { + return strtolower($val); + }, $options['only']); + + if (!in_array($this->request->action(), $only)) { + return; + } + } elseif (isset($options['except'])) { + if (is_string($options['except'])) { + $options['except'] = explode(',', $options['except']); + } + + $except = array_map(function ($val) { + return strtolower($val); + }, $options['except']); + + if (in_array($this->request->action(), $except)) { + return; + } + } + + call_user_func([$this, $method]); + } + + /** + * 加载模板输出 + * @access protected + * @param string $template 模板文件名 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @return mixed + */ + protected function fetch($template = '', $vars = [], $config = []) + { + return $this->view->fetch($template, $vars, $config); + } + + /** + * 渲染内容输出 + * @access protected + * @param string $content 模板内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @return mixed + */ + protected function display($content = '', $vars = [], $config = []) + { + return $this->view->display($content, $vars, $config); + } + + /** + * 模板变量赋值 + * @access protected + * @param mixed $name 要显示的模板变量 + * @param mixed $value 变量的值 + * @return $this + */ + protected function assign($name, $value = '') + { + $this->view->assign($name, $value); + + return $this; + } + + /** + * 视图过滤 + * @access protected + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + protected function filter($filter) + { + $this->view->filter($filter); + + return $this; + } + + /** + * 初始化模板引擎 + * @access protected + * @param array|string $engine 引擎参数 + * @return $this + */ + protected function engine($engine) + { + $this->view->engine($engine); + + return $this; + } + + /** + * 设置验证失败后是否抛出异常 + * @access protected + * @param bool $fail 是否抛出异常 + * @return $this + */ + protected function validateFailException($fail = true) + { + $this->failException = $fail; + + return $this; + } + + /** + * 验证数据 + * @access protected + * @param array $data 数据 + * @param string|array $validate 验证器名或者验证规则数组 + * @param array $message 提示信息 + * @param bool $batch 是否批量验证 + * @param mixed $callback 回调方法(闭包) + * @return array|string|true + * @throws ValidateException + */ + protected function validate($data, $validate, $message = [], $batch = false, $callback = null) + { + if (is_array($validate)) { + $v = $this->app->validate(); + $v->rule($validate); + } else { + if (strpos($validate, '.')) { + // 支持场景 + list($validate, $scene) = explode('.', $validate); + } + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + // 是否批量验证 + if ($batch || $this->batchValidate) { + $v->batch(true); + } + + if (is_array($message)) { + $v->message($message); + } + + if ($callback && is_callable($callback)) { + call_user_func_array($callback, [$v, &$data]); + } + + if (!$v->check($data)) { + if ($this->failException) { + throw new ValidateException($v->getError()); + } + return $v->getError(); + } + + return true; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request']); + + return $data; + } +} diff --git a/thinkphp/library/think/Cookie.php b/thinkphp/library/think/Cookie.php new file mode 100644 index 0000000..6a9fb1e --- /dev/null +++ b/thinkphp/library/think/Cookie.php @@ -0,0 +1,268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Cookie +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // cookie 名称前缀 + 'prefix' => '', + // cookie 保存时间 + 'expire' => 0, + // cookie 保存路径 + 'path' => '/', + // cookie 有效域名 + 'domain' => '', + // cookie 启用安全传输 + 'secure' => false, + // httponly设置 + 'httponly' => false, + // 是否使用 setcookie + 'setcookie' => true, + ]; + + /** + * 构造方法 + * @access public + */ + public function __construct(array $config = []) + { + $this->init($config); + } + + /** + * Cookie初始化 + * @access public + * @param array $config + * @return void + */ + public function init(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + + if (!empty($this->config['httponly']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.cookie_httponly', 1); + } + } + + public static function __make(Config $config) + { + return new static($config->pull('cookie')); + } + + /** + * 设置或者获取cookie作用域(前缀) + * @access public + * @param string $prefix + * @return string|void + */ + public function prefix($prefix = '') + { + if (empty($prefix)) { + return $this->config['prefix']; + } + + $this->config['prefix'] = $prefix; + } + + /** + * Cookie 设置、获取、删除 + * + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function set($name, $value = '', $option = null) + { + // 参数设置(会覆盖黙认设置) + if (!is_null($option)) { + if (is_numeric($option)) { + $option = ['expire' => $option]; + } elseif (is_string($option)) { + parse_str($option, $option); + } + + $config = array_merge($this->config, array_change_key_case($option)); + } else { + $config = $this->config; + } + + $name = $config['prefix'] . $name; + + // 设置cookie + if (is_array($value)) { + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'encode'); + $value = 'think:' . json_encode($value); + } + + $expire = !empty($config['expire']) ? $_SERVER['REQUEST_TIME'] + intval($config['expire']) : 0; + + if ($config['setcookie']) { + $this->setCookie($name, $value, $expire, $config); + } + + $_COOKIE[$name] = $value; + } + + /** + * Cookie 设置保存 + * + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param array $option 可选参数 + * @return void + */ + protected function setCookie($name, $value, $expire, $option = []) + { + setcookie($name, $value, $expire, $option['path'], $option['domain'], $option['secure'], $option['httponly']); + } + + /** + * 永久保存Cookie数据 + * @access public + * @param string $name cookie名称 + * @param mixed $value cookie值 + * @param mixed $option 可选参数 可能会是 null|integer|string + * @return void + */ + public function forever($name, $value = '', $option = null) + { + if (is_null($option) || is_numeric($option)) { + $option = []; + } + + $option['expire'] = 315360000; + + $this->set($name, $value, $option); + } + + /** + * 判断Cookie数据 + * @access public + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return bool + */ + public function has($name, $prefix = null) + { + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $name = $prefix . $name; + + return isset($_COOKIE[$name]); + } + + /** + * Cookie获取 + * @access public + * @param string $name cookie名称 留空获取全部 + * @param string|null $prefix cookie前缀 + * @return mixed + */ + public function get($name = '', $prefix = null) + { + $prefix = !is_null($prefix) ? $prefix : $this->config['prefix']; + $key = $prefix . $name; + + if ('' == $name) { + if ($prefix) { + $value = []; + foreach ($_COOKIE as $k => $val) { + if (0 === strpos($k, $prefix)) { + $value[$k] = $val; + } + } + } else { + $value = $_COOKIE; + } + } elseif (isset($_COOKIE[$key])) { + $value = $_COOKIE[$key]; + + if (0 === strpos($value, 'think:')) { + $value = substr($value, 6); + $value = json_decode($value, true); + array_walk_recursive($value, [$this, 'jsonFormatProtect'], 'decode'); + } + } else { + $value = null; + } + + return $value; + } + + /** + * Cookie删除 + * @access public + * @param string $name cookie名称 + * @param string|null $prefix cookie前缀 + * @return void + */ + public function delete($name, $prefix = null) + { + $config = $this->config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + $name = $prefix . $name; + + if ($config['setcookie']) { + $this->setcookie($name, '', $_SERVER['REQUEST_TIME'] - 3600, $config); + } + + // 删除指定cookie + unset($_COOKIE[$name]); + } + + /** + * Cookie清空 + * @access public + * @param string|null $prefix cookie前缀 + * @return void + */ + public function clear($prefix = null) + { + // 清除指定前缀的所有cookie + if (empty($_COOKIE)) { + return; + } + + // 要删除的cookie前缀,不指定则删除config设置的指定前缀 + $config = $this->config; + $prefix = !is_null($prefix) ? $prefix : $config['prefix']; + + if ($prefix) { + // 如果前缀为空字符串将不作处理直接返回 + foreach ($_COOKIE as $key => $val) { + if (0 === strpos($key, $prefix)) { + if ($config['setcookie']) { + $this->setcookie($key, '', $_SERVER['REQUEST_TIME'] - 3600, $config); + } + unset($_COOKIE[$key]); + } + } + } + + return; + } + + private function jsonFormatProtect(&$val, $key, $type = 'encode') + { + if (!empty($val) && true !== $val) { + $val = 'decode' == $type ? urldecode($val) : urlencode($val); + } + } + +} diff --git a/thinkphp/library/think/Db.php b/thinkphp/library/think/Db.php new file mode 100644 index 0000000..9280eac --- /dev/null +++ b/thinkphp/library/think/Db.php @@ -0,0 +1,197 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\db\Connection; + +/** + * Class Db + * @package think + * @method \think\db\Query master() static 从主服务器读取数据 + * @method \think\db\Query readMaster(bool $all = false) static 后续从主服务器读取数据 + * @method \think\db\Query table(string $table) static 指定数据表(含前缀) + * @method \think\db\Query name(string $name) static 指定数据表(不含前缀) + * @method \think\db\Expression raw(string $value) static 使用表达式设置数据 + * @method \think\db\Query where(mixed $field, string $op = null, mixed $condition = null) static 查询条件 + * @method \think\db\Query whereRaw(string $where, array $bind = []) static 表达式查询 + * @method \think\db\Query whereExp(string $field, string $condition, array $bind = []) static 字段表达式查询 + * @method \think\db\Query when(mixed $condition, mixed $query, mixed $otherwise = null) static 条件查询 + * @method \think\db\Query join(mixed $join, mixed $condition = null, string $type = 'INNER') static JOIN查询 + * @method \think\db\Query view(mixed $join, mixed $field = null, mixed $on = null, string $type = 'INNER') static 视图查询 + * @method \think\db\Query field(mixed $field, boolean $except = false) static 指定查询字段 + * @method \think\db\Query fieldRaw(string $field, array $bind = []) static 指定查询字段 + * @method \think\db\Query union(mixed $union, boolean $all = false) static UNION查询 + * @method \think\db\Query limit(mixed $offset, integer $length = null) static 查询LIMIT + * @method \think\db\Query order(mixed $field, string $order = null) static 查询ORDER + * @method \think\db\Query orderRaw(string $field, array $bind = []) static 查询ORDER + * @method \think\db\Query cache(mixed $key = null , integer $expire = null) static 设置查询缓存 + * @method \think\db\Query withAttr(string $name,callable $callback = null) static 使用获取器获取数据 + * @method mixed value(string $field) static 获取某个字段的值 + * @method array column(string $field, string $key = '') static 获取某个列的值 + * @method mixed find(mixed $data = null) static 查询单个记录 + * @method mixed select(mixed $data = null) static 查询多个记录 + * @method integer insert(array $data, boolean $replace = false, boolean $getLastInsID = false, string $sequence = null) static 插入一条记录 + * @method integer insertGetId(array $data, boolean $replace = false, string $sequence = null) static 插入一条记录并返回自增ID + * @method integer insertAll(array $dataSet) static 插入多条记录 + * @method integer update(array $data) static 更新记录 + * @method integer delete(mixed $data = null) static 删除记录 + * @method boolean chunk(integer $count, callable $callback, string $column = null) static 分块获取数据 + * @method \Generator cursor(mixed $data = null) static 使用游标查找记录 + * @method mixed query(string $sql, array $bind = [], boolean $master = false, bool $pdo = false) static SQL查询 + * @method integer execute(string $sql, array $bind = [], boolean $fetch = false, boolean $getLastInsID = false, string $sequence = null) static SQL执行 + * @method \think\Paginator paginate(integer $listRows = 15, mixed $simple = null, array $config = []) static 分页查询 + * @method mixed transaction(callable $callback) static 执行数据库事务 + * @method void startTrans() static 启动事务 + * @method void commit() static 用于非自动提交状态下面的查询提交 + * @method void rollback() static 事务回滚 + * @method boolean batchQuery(array $sqlArray) static 批处理执行SQL语句 + * @method string getLastInsID(string $sequence = null) static 获取最近插入的ID + */ +class Db +{ + /** + * 当前数据库连接对象 + * @var Connection + */ + protected static $connection; + + /** + * 数据库配置 + * @var array + */ + protected static $config = []; + + /** + * 查询次数 + * @var integer + */ + public static $queryTimes = 0; + + /** + * 执行次数 + * @var integer + */ + public static $executeTimes = 0; + + /** + * 配置 + * @access public + * @param mixed $config + * @return void + */ + public static function init($config = []) + { + self::$config = $config; + + if (empty($config['query'])) { + self::$config['query'] = '\\think\\db\\Query'; + } + } + + /** + * 获取数据库配置 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public static function getConfig($name = '') + { + if ('' === $name) { + return self::$config; + } + + return isset(self::$config[$name]) ? self::$config[$name] : null; + } + + /** + * 切换数据库连接 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @param string $query 查询对象类名 + * @return mixed 返回查询对象实例 + * @throws Exception + */ + public static function connect($config = [], $name = false, $query = '') + { + // 解析配置参数 + $options = self::parseConfig($config ?: self::$config); + + $query = $query ?: $options['query']; + + // 创建数据库连接对象实例 + self::$connection = Connection::instance($options, $name); + + return new $query(self::$connection); + } + + /** + * 数据库连接参数解析 + * @access private + * @param mixed $config + * @return array + */ + private static function parseConfig($config) + { + if (is_string($config) && false === strpos($config, '/')) { + // 支持读取配置参数 + $config = isset(self::$config[$config]) ? self::$config[$config] : self::$config; + } + + $result = is_string($config) ? self::parseDsnConfig($config) : $config; + + if (empty($result['query'])) { + $result['query'] = self::$config['query']; + } + + return $result; + } + + /** + * DSN解析 + * 格式: mysql://username:passwd@localhost:3306/DbName?param1=val1¶m2=val2#utf8 + * @access private + * @param string $dsnStr + * @return array + */ + private static function parseDsnConfig($dsnStr) + { + $info = parse_url($dsnStr); + + if (!$info) { + return []; + } + + $dsn = [ + 'type' => $info['scheme'], + 'username' => isset($info['user']) ? $info['user'] : '', + 'password' => isset($info['pass']) ? $info['pass'] : '', + 'hostname' => isset($info['host']) ? $info['host'] : '', + 'hostport' => isset($info['port']) ? $info['port'] : '', + 'database' => !empty($info['path']) ? ltrim($info['path'], '/') : '', + 'charset' => isset($info['fragment']) ? $info['fragment'] : 'utf8', + ]; + + if (isset($info['query'])) { + parse_str($info['query'], $dsn['params']); + } else { + $dsn['params'] = []; + } + + return $dsn; + } + + public static function __callStatic($method, $args) + { + return call_user_func_array([static::connect(), $method], $args); + } +} diff --git a/thinkphp/library/think/Debug.php b/thinkphp/library/think/Debug.php new file mode 100644 index 0000000..776e178 --- /dev/null +++ b/thinkphp/library/think/Debug.php @@ -0,0 +1,278 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\model\Collection as ModelCollection; +use think\response\Redirect; + +class Debug +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 区间时间信息 + * @var array + */ + protected $info = []; + + /** + * 区间内存信息 + * @var array + */ + protected $mem = []; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('trace')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 记录时间(微秒)和内存使用情况 + * @access public + * @param string $name 标记位置 + * @param mixed $value 标记值 留空则取当前 time 表示仅记录时间 否则同时记录时间和内存 + * @return void + */ + public function remark($name, $value = '') + { + // 记录时间和内存使用 + $this->info[$name] = is_float($value) ? $value : microtime(true); + + if ('time' != $value) { + $this->mem['mem'][$name] = is_float($value) ? $value : memory_get_usage(); + $this->mem['peak'][$name] = memory_get_peak_usage(); + } + } + + /** + * 统计某个区间的时间(微秒)使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return integer + */ + public function getRangeTime($start, $end, $dec = 6) + { + if (!isset($this->info[$end])) { + $this->info[$end] = microtime(true); + } + + return number_format(($this->info[$end] - $this->info[$start]), $dec); + } + + /** + * 统计从开始到统计时的时间(微秒)使用情况 + * @access public + * @param integer|string $dec 小数位 + * @return integer + */ + public function getUseTime($dec = 6) + { + return number_format((microtime(true) - $this->app->getBeginTime()), $dec); + } + + /** + * 获取当前访问的吞吐率情况 + * @access public + * @return string + */ + public function getThroughputRate() + { + return number_format(1 / $this->getUseTime(), 2) . 'req/s'; + } + + /** + * 记录区间的内存使用情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return string + */ + public function getRangeMem($start, $end, $dec = 2) + { + if (!isset($this->mem['mem'][$end])) { + $this->mem['mem'][$end] = memory_get_usage(); + } + + $size = $this->mem['mem'][$end] - $this->mem['mem'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计从开始到统计时的内存使用情况 + * @access public + * @param integer|string $dec 小数位 + * @return string + */ + public function getUseMem($dec = 2) + { + $size = memory_get_usage() - $this->app->getBeginMem(); + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 统计区间的内存峰值情况 + * @access public + * @param string $start 开始标签 + * @param string $end 结束标签 + * @param integer|string $dec 小数位 + * @return string + */ + public function getMemPeak($start, $end, $dec = 2) + { + if (!isset($this->mem['peak'][$end])) { + $this->mem['peak'][$end] = memory_get_peak_usage(); + } + + $size = $this->mem['peak'][$end] - $this->mem['peak'][$start]; + $a = ['B', 'KB', 'MB', 'GB', 'TB']; + $pos = 0; + + while ($size >= 1024) { + $size /= 1024; + $pos++; + } + + return round($size, $dec) . " " . $a[$pos]; + } + + /** + * 获取文件加载信息 + * @access public + * @param bool $detail 是否显示详细 + * @return integer|array + */ + public function getFile($detail = false) + { + if ($detail) { + $files = get_included_files(); + $info = []; + + foreach ($files as $key => $file) { + $info[] = $file . ' ( ' . number_format(filesize($file) / 1024, 2) . ' KB )'; + } + + return $info; + } + + return count(get_included_files()); + } + + /** + * 浏览器友好的变量输出 + * @access public + * @param mixed $var 变量 + * @param boolean $echo 是否输出 默认为true 如果为false 则返回输出字符串 + * @param string $label 标签 默认为空 + * @param integer $flags htmlspecialchars flags + * @return void|string + */ + public function dump($var, $echo = true, $label = null, $flags = ENT_SUBSTITUTE) + { + $label = (null === $label) ? '' : rtrim($label) . ':'; + if ($var instanceof Model || $var instanceof ModelCollection) { + $var = $var->toArray(); + } + + ob_start(); + var_dump($var); + + $output = ob_get_clean(); + $output = preg_replace('/\]\=\>\n(\s+)/m', '] => ', $output); + + if (PHP_SAPI == 'cli') { + $output = PHP_EOL . $label . $output . PHP_EOL; + } else { + if (!extension_loaded('xdebug')) { + $output = htmlspecialchars($output, $flags); + } + $output = '
' . $label . $output . '
'; + } + if ($echo) { + echo($output); + return; + } + return $output; + } + + public function inject(Response $response, &$content) + { + $config = $this->config; + $type = isset($config['type']) ? $config['type'] : 'Html'; + + unset($config['type']); + + $trace = Loader::factory($type, '\\think\\debug\\', $config); + + if ($response instanceof Redirect) { + //TODO 记录 + } else { + $output = $trace->output($response, $this->app['log']->getLog()); + if (is_string($output)) { + // trace调试信息注入 + $pos = strripos($content, ''); + if (false !== $pos) { + $content = substr($content, 0, $pos) . $output . substr($content, $pos); + } else { + $content = $content . $output; + } + } + } + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/thinkphp/library/think/Env.php b/thinkphp/library/think/Env.php new file mode 100644 index 0000000..eaeee94 --- /dev/null +++ b/thinkphp/library/think/Env.php @@ -0,0 +1,113 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Env +{ + /** + * 环境变量数据 + * @var array + */ + protected $data = []; + + public function __construct() + { + $this->data = $_ENV; + } + + /** + * 读取环境变量定义文件 + * @access public + * @param string $file 环境变量定义文件 + * @return void + */ + public function load($file) + { + $env = parse_ini_file($file, true); + $this->set($env); + } + + /** + * 获取环境变量值 + * @access public + * @param string $name 环境变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name = null, $default = null, $php_prefix = true) + { + if (is_null($name)) { + return $this->data; + } + + $name = strtoupper(str_replace('.', '_', $name)); + + if (isset($this->data[$name])) { + return $this->data[$name]; + } + + return $this->getEnv($name, $default, $php_prefix); + } + + protected function getEnv($name, $default = null, $php_prefix = true) + { + if ($php_prefix) { + $name = 'PHP_' . $name; + } + + $result = getenv($name); + + if (false === $result) { + return $default; + } + + if ('false' === $result) { + $result = false; + } elseif ('true' === $result) { + $result = true; + } + + if (!isset($this->data[$name])) { + $this->data[$name] = $result; + } + + return $result; + } + + /** + * 设置环境变量值 + * @access public + * @param string|array $env 环境变量 + * @param mixed $value 值 + * @return void + */ + public function set($env, $value = null) + { + if (is_array($env)) { + $env = array_change_key_case($env, CASE_UPPER); + + foreach ($env as $key => $val) { + if (is_array($val)) { + foreach ($val as $k => $v) { + $this->data[$key . '_' . strtoupper($k)] = $v; + } + } else { + $this->data[$key] = $val; + } + } + } else { + $name = strtoupper(str_replace('.', '_', $env)); + + $this->data[$name] = $value; + } + } +} diff --git a/thinkphp/library/think/Error.php b/thinkphp/library/think/Error.php new file mode 100644 index 0000000..ea3328e --- /dev/null +++ b/thinkphp/library/think/Error.php @@ -0,0 +1,147 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\console\Output as ConsoleOutput; +use think\exception\ErrorException; +use think\exception\Handle; +use think\exception\ThrowableError; + +class Error +{ + /** + * 配置参数 + * @var array + */ + protected static $exceptionHandler; + + /** + * 注册异常处理 + * @access public + * @return void + */ + public static function register() + { + error_reporting(E_ALL); + set_error_handler([__CLASS__, 'appError']); + set_exception_handler([__CLASS__, 'appException']); + register_shutdown_function([__CLASS__, 'appShutdown']); + } + + /** + * Exception Handler + * @access public + * @param \Exception|\Throwable $e + */ + public static function appException($e) + { + if (!$e instanceof \Exception) { + $e = new ThrowableError($e); + } + + self::getExceptionHandler()->report($e); + + if (PHP_SAPI == 'cli') { + self::getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); + } else { + self::getExceptionHandler()->render($e)->send(); + } + } + + /** + * Error Handler + * @access public + * @param integer $errno 错误编号 + * @param integer $errstr 详细错误信息 + * @param string $errfile 出错的文件 + * @param integer $errline 出错行号 + * @throws ErrorException + */ + public static function appError($errno, $errstr, $errfile = '', $errline = 0) + { + $exception = new ErrorException($errno, $errstr, $errfile, $errline); + if (error_reporting() & $errno) { + // 将错误信息托管至 think\exception\ErrorException + throw $exception; + } + + self::getExceptionHandler()->report($exception); + } + + /** + * Shutdown Handler + * @access public + */ + public static function appShutdown() + { + if (!is_null($error = error_get_last()) && self::isFatal($error['type'])) { + // 将错误信息托管至think\ErrorException + $exception = new ErrorException($error['type'], $error['message'], $error['file'], $error['line']); + + self::appException($exception); + } + + // 写入日志 + Container::get('log')->save(); + } + + /** + * 确定错误类型是否致命 + * + * @access protected + * @param int $type + * @return bool + */ + protected static function isFatal($type) + { + return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]); + } + + /** + * 设置异常处理类 + * + * @access public + * @param mixed $handle + * @return void + */ + public static function setExceptionHandler($handle) + { + self::$exceptionHandler = $handle; + } + + /** + * Get an instance of the exception handler. + * + * @access public + * @return Handle + */ + public static function getExceptionHandler() + { + static $handle; + + if (!$handle) { + // 异常处理handle + $class = self::$exceptionHandler; + + if ($class && is_string($class) && class_exists($class) && is_subclass_of($class, "\\think\\exception\\Handle")) { + $handle = new $class; + } else { + $handle = new Handle; + if ($class instanceof \Closure) { + $handle->setRender($class); + } + } + } + + return $handle; + } +} diff --git a/thinkphp/library/think/Exception.php b/thinkphp/library/think/Exception.php new file mode 100644 index 0000000..414a090 --- /dev/null +++ b/thinkphp/library/think/Exception.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Exception extends \Exception +{ + + /** + * 保存异常页面显示的额外Debug数据 + * @var array + */ + protected $data = []; + + /** + * 设置异常额外的Debug数据 + * 数据将会显示为下面的格式 + * + * Exception Data + * -------------------------------------------------- + * Label 1 + * key1 value1 + * key2 value2 + * Label 2 + * key1 value1 + * key2 value2 + * + * @access protected + * @param string $label 数据分类,用于异常页面显示 + * @param array $data 需要显示的数据,必须为关联数组 + */ + final protected function setData($label, array $data) + { + $this->data[$label] = $data; + } + + /** + * 获取异常额外Debug数据 + * 主要用于输出到异常页面便于调试 + * @access public + * @return array 由setData设置的Debug数据 + */ + final public function getData() + { + return $this->data; + } + +} diff --git a/thinkphp/library/think/Facade.php b/thinkphp/library/think/Facade.php new file mode 100644 index 0000000..ac5ae28 --- /dev/null +++ b/thinkphp/library/think/Facade.php @@ -0,0 +1,125 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Facade +{ + /** + * 绑定对象 + * @var array + */ + protected static $bind = []; + + /** + * 始终创建新的对象实例 + * @var bool + */ + protected static $alwaysNewInstance; + + /** + * 绑定类的静态代理 + * @static + * @access public + * @param string|array $name 类标识 + * @param string $class 类名 + * @return object + */ + public static function bind($name, $class = null) + { + if (__CLASS__ != static::class) { + return self::__callStatic('bind', func_get_args()); + } + + if (is_array($name)) { + self::$bind = array_merge(self::$bind, $name); + } else { + self::$bind[$name] = $class; + } + } + + /** + * 创建Facade实例 + * @static + * @access protected + * @param string $class 类名或标识 + * @param array $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return object + */ + protected static function createFacade($class = '', $args = [], $newInstance = false) + { + $class = $class ?: static::class; + + $facadeClass = static::getFacadeClass(); + + if ($facadeClass) { + $class = $facadeClass; + } elseif (isset(self::$bind[$class])) { + $class = self::$bind[$class]; + } + + if (static::$alwaysNewInstance) { + $newInstance = true; + } + + return Container::getInstance()->make($class, $args, $newInstance); + } + + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + {} + + /** + * 带参数实例化当前Facade类 + * @access public + * @return mixed + */ + public static function instance(...$args) + { + if (__CLASS__ != static::class) { + return self::createFacade('', $args); + } + } + + /** + * 调用类的实例 + * @access public + * @param string $class 类名或者标识 + * @param array|true $args 变量 + * @param bool $newInstance 是否每次创建新的实例 + * @return mixed + */ + public static function make($class, $args = [], $newInstance = false) + { + if (__CLASS__ != static::class) { + return self::__callStatic('make', func_get_args()); + } + + if (true === $args) { + // 总是创建新的实例化对象 + $newInstance = true; + $args = []; + } + + return self::createFacade($class, $args, $newInstance); + } + + // 调用实际类的方法 + public static function __callStatic($method, $params) + { + return call_user_func_array([static::createFacade(), $method], $params); + } +} diff --git a/thinkphp/library/think/File.php b/thinkphp/library/think/File.php new file mode 100644 index 0000000..b290609 --- /dev/null +++ b/thinkphp/library/think/File.php @@ -0,0 +1,496 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use SplFileObject; + +class File extends SplFileObject +{ + /** + * 错误信息 + * @var string + */ + private $error = ''; + + /** + * 当前完整文件名 + * @var string + */ + protected $filename; + + /** + * 上传文件名 + * @var string + */ + protected $saveName; + + /** + * 上传文件命名规则 + * @var string + */ + protected $rule = 'date'; + + /** + * 上传文件验证规则 + * @var array + */ + protected $validate = []; + + /** + * 是否单元测试 + * @var bool + */ + protected $isTest; + + /** + * 上传文件信息 + * @var array + */ + protected $info = []; + + /** + * 文件hash规则 + * @var array + */ + protected $hash = []; + + public function __construct($filename, $mode = 'r') + { + parent::__construct($filename, $mode); + + $this->filename = $this->getRealPath() ?: $this->getPathname(); + } + + /** + * 是否测试 + * @access public + * @param bool $test 是否测试 + * @return $this + */ + public function isTest($test = false) + { + $this->isTest = $test; + + return $this; + } + + /** + * 设置上传信息 + * @access public + * @param array $info 上传文件信息 + * @return $this + */ + public function setUploadInfo($info) + { + $this->info = $info; + + return $this; + } + + /** + * 获取上传文件的信息 + * @access public + * @param string $name + * @return array|string + */ + public function getInfo($name = '') + { + return isset($this->info[$name]) ? $this->info[$name] : $this->info; + } + + /** + * 获取上传文件的文件名 + * @access public + * @return string + */ + public function getSaveName() + { + return $this->saveName; + } + + /** + * 设置上传文件的保存文件名 + * @access public + * @param string $saveName + * @return $this + */ + public function setSaveName($saveName) + { + $this->saveName = $saveName; + + return $this; + } + + /** + * 获取文件的哈希散列值 + * @access public + * @param string $type + * @return string + */ + public function hash($type = 'sha1') + { + if (!isset($this->hash[$type])) { + $this->hash[$type] = hash_file($type, $this->filename); + } + + return $this->hash[$type]; + } + + /** + * 检查目录是否可写 + * @access protected + * @param string $path 目录 + * @return boolean + */ + protected function checkPath($path) + { + if (is_dir($path)) { + return true; + } + + if (mkdir($path, 0755, true)) { + return true; + } + + $this->error = ['directory {:path} creation failed', ['path' => $path]]; + return false; + } + + /** + * 获取文件类型信息 + * @access public + * @return string + */ + public function getMime() + { + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $this->filename); + } + + /** + * 设置文件的命名规则 + * @access public + * @param string $rule 文件命名规则 + * @return $this + */ + public function rule($rule) + { + $this->rule = $rule; + + return $this; + } + + /** + * 设置上传文件的验证规则 + * @access public + * @param array $rule 验证规则 + * @return $this + */ + public function validate($rule = []) + { + $this->validate = $rule; + + return $this; + } + + /** + * 检测是否合法的上传文件 + * @access public + * @return bool + */ + public function isValid() + { + if ($this->isTest) { + return is_file($this->filename); + } + + return is_uploaded_file($this->filename); + } + + /** + * 检测上传文件 + * @access public + * @param array $rule 验证规则 + * @return bool + */ + public function check($rule = []) + { + $rule = $rule ?: $this->validate; + + if ((isset($rule['size']) && !$this->checkSize($rule['size'])) + || (isset($rule['type']) && !$this->checkMime($rule['type'])) + || (isset($rule['ext']) && !$this->checkExt($rule['ext'])) + || !$this->checkImg()) { + return false; + } + + return true; + } + + /** + * 检测上传文件后缀 + * @access public + * @param array|string $ext 允许后缀 + * @return bool + */ + public function checkExt($ext) + { + if (is_string($ext)) { + $ext = explode(',', $ext); + } + + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + if (!in_array($extension, $ext)) { + $this->error = 'extensions to upload is not allowed'; + return false; + } + + return true; + } + + /** + * 检测图像文件 + * @access public + * @return bool + */ + public function checkImg() + { + $extension = strtolower(pathinfo($this->getInfo('name'), PATHINFO_EXTENSION)); + + /* 对图像文件进行严格检测 */ + if (in_array($extension, ['gif', 'jpg', 'jpeg', 'bmp', 'png', 'swf']) && !in_array($this->getImageType($this->filename), [1, 2, 3, 4, 6, 13])) { + $this->error = 'illegal image files'; + return false; + } + + return true; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 检测上传文件大小 + * @access public + * @param integer $size 最大大小 + * @return bool + */ + public function checkSize($size) + { + if ($this->getSize() > $size) { + $this->error = 'filesize not match'; + return false; + } + + return true; + } + + /** + * 检测上传文件类型 + * @access public + * @param array|string $mime 允许类型 + * @return bool + */ + public function checkMime($mime) + { + if (is_string($mime)) { + $mime = explode(',', $mime); + } + + if (!in_array(strtolower($this->getMime()), $mime)) { + $this->error = 'mimetype to upload is not allowed'; + return false; + } + + return true; + } + + /** + * 移动文件 + * @access public + * @param string $path 保存路径 + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param boolean $replace 同名文件是否覆盖 + * @param bool $autoAppendExt 自动补充扩展名 + * @return false|File false-失败 否则返回File实例 + */ + public function move($path, $savename = true, $replace = true, $autoAppendExt = true) + { + // 文件上传失败,捕获错误代码 + if (!empty($this->info['error'])) { + $this->error($this->info['error']); + return false; + } + + // 检测合法性 + if (!$this->isValid()) { + $this->error = 'upload illegal files'; + return false; + } + + // 验证上传 + if (!$this->check()) { + return false; + } + + $path = rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; + // 文件保存命名规则 + $saveName = $this->buildSaveName($savename, $autoAppendExt); + $filename = $path . $saveName; + + // 检测目录 + if (false === $this->checkPath(dirname($filename))) { + return false; + } + + /* 不覆盖同名文件 */ + if (!$replace && is_file($filename)) { + $this->error = ['has the same filename: {:filename}', ['filename' => $filename]]; + return false; + } + + /* 移动文件 */ + if ($this->isTest) { + rename($this->filename, $filename); + } elseif (!move_uploaded_file($this->filename, $filename)) { + $this->error = 'upload write error'; + return false; + } + + // 返回 File对象实例 + $file = new self($filename); + $file->setSaveName($saveName); + $file->setUploadInfo($this->info); + + return $file; + } + + /** + * 获取保存文件名 + * @access protected + * @param string|bool $savename 保存的文件名 默认自动生成 + * @param bool $autoAppendExt 自动补充扩展名 + * @return string + */ + protected function buildSaveName($savename, $autoAppendExt = true) + { + if (true === $savename) { + // 自动生成文件名 + $savename = $this->autoBuildName(); + } elseif ('' === $savename || false === $savename) { + // 保留原文件名 + $savename = $this->getInfo('name'); + } + + if ($autoAppendExt && false === strpos($savename, '.')) { + $savename .= '.' . pathinfo($this->getInfo('name'), PATHINFO_EXTENSION); + } + + return $savename; + } + + /** + * 自动生成文件名 + * @access protected + * @return string + */ + protected function autoBuildName() + { + if ($this->rule instanceof \Closure) { + $savename = call_user_func_array($this->rule, [$this]); + } else { + switch ($this->rule) { + case 'date': + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + break; + default: + if (in_array($this->rule, hash_algos())) { + $hash = $this->hash($this->rule); + $savename = substr($hash, 0, 2) . DIRECTORY_SEPARATOR . substr($hash, 2); + } elseif (is_callable($this->rule)) { + $savename = call_user_func($this->rule); + } else { + $savename = date('Ymd') . DIRECTORY_SEPARATOR . md5(microtime(true)); + } + } + } + + return $savename; + } + + /** + * 获取错误代码信息 + * @access private + * @param int $errorNo 错误号 + */ + private function error($errorNo) + { + switch ($errorNo) { + case 1: + case 2: + $this->error = 'upload File size exceeds the maximum value'; + break; + case 3: + $this->error = 'only the portion of file is uploaded'; + break; + case 4: + $this->error = 'no file to uploaded'; + break; + case 6: + $this->error = 'upload temp dir not found'; + break; + case 7: + $this->error = 'file write error'; + break; + default: + $this->error = 'unknown upload error'; + } + } + + /** + * 获取错误信息(支持多语言) + * @access public + * @return string + */ + public function getError() + { + $lang = Container::get('lang'); + + if (is_array($this->error)) { + list($msg, $vars) = $this->error; + } else { + $msg = $this->error; + $vars = []; + } + + return $lang->has($msg) ? $lang->get($msg, $vars) : $msg; + } + + public function __call($method, $args) + { + return $this->hash($method); + } +} diff --git a/thinkphp/library/think/Hook.php b/thinkphp/library/think/Hook.php new file mode 100644 index 0000000..1d01141 --- /dev/null +++ b/thinkphp/library/think/Hook.php @@ -0,0 +1,220 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Hook +{ + /** + * 钩子行为定义 + * @var array + */ + private $tags = []; + + /** + * 绑定行为列表 + * @var array + */ + protected $bind = []; + + /** + * 入口方法名称 + * @var string + */ + private static $portal = 'run'; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + /** + * 指定入口方法名称 + * @access public + * @param string $name 方法名 + * @return $this + */ + public function portal($name) + { + self::$portal = $name; + return $this; + } + + /** + * 指定行为标识 便于调用 + * @access public + * @param string|array $name 行为标识 + * @param mixed $behavior 行为 + * @return $this + */ + public function alias($name, $behavior = null) + { + if (is_array($name)) { + $this->bind = array_merge($this->bind, $name); + } else { + $this->bind[$name] = $behavior; + } + + return $this; + } + + /** + * 动态添加行为扩展到某个标签 + * @access public + * @param string $tag 标签名称 + * @param mixed $behavior 行为名称 + * @param bool $first 是否放到开头执行 + * @return void + */ + public function add($tag, $behavior, $first = false) + { + isset($this->tags[$tag]) || $this->tags[$tag] = []; + + if (is_array($behavior) && !is_callable($behavior)) { + if (!array_key_exists('_overlay', $behavior)) { + $this->tags[$tag] = array_merge($this->tags[$tag], $behavior); + } else { + unset($behavior['_overlay']); + $this->tags[$tag] = $behavior; + } + } elseif ($first) { + array_unshift($this->tags[$tag], $behavior); + } else { + $this->tags[$tag][] = $behavior; + } + } + + /** + * 批量导入插件 + * @access public + * @param array $tags 插件信息 + * @param bool $recursive 是否递归合并 + * @return void + */ + public function import(array $tags, $recursive = true) + { + if ($recursive) { + foreach ($tags as $tag => $behavior) { + $this->add($tag, $behavior); + } + } else { + $this->tags = $tags + $this->tags; + } + } + + /** + * 获取插件信息 + * @access public + * @param string $tag 插件位置 留空获取全部 + * @return array + */ + public function get($tag = '') + { + if (empty($tag)) { + //获取全部的插件信息 + return $this->tags; + } + + return array_key_exists($tag, $this->tags) ? $this->tags[$tag] : []; + } + + /** + * 监听标签的行为 + * @access public + * @param string $tag 标签名称 + * @param mixed $params 传入参数 + * @param bool $once 只获取一个有效返回值 + * @return mixed + */ + public function listen($tag, $params = null, $once = false) + { + $results = []; + $tags = $this->get($tag); + + foreach ($tags as $key => $name) { + $results[$key] = $this->execTag($name, $tag, $params); + + if (false === $results[$key] || (!is_null($results[$key]) && $once)) { + break; + } + } + + return $once ? end($results) : $results; + } + + /** + * 执行行为 + * @access public + * @param mixed $class 行为 + * @param mixed $params 参数 + * @return mixed + */ + public function exec($class, $params = null) + { + if ($class instanceof \Closure || is_array($class)) { + $method = $class; + } else { + if (isset($this->bind[$class])) { + $class = $this->bind[$class]; + } + $method = [$class, self::$portal]; + } + + return $this->app->invoke($method, [$params]); + } + + /** + * 执行某个标签的行为 + * @access protected + * @param mixed $class 要执行的行为 + * @param string $tag 方法名(标签名) + * @param mixed $params 参数 + * @return mixed + */ + protected function execTag($class, $tag = '', $params = null) + { + $method = Loader::parseName($tag, 1, false); + + if ($class instanceof \Closure) { + $call = $class; + $class = 'Closure'; + } elseif (is_array($class) || strpos($class, '::')) { + $call = $class; + } else { + $obj = Container::get($class); + + if (!is_callable([$obj, $method])) { + $method = self::$portal; + } + + $call = [$class, $method]; + $class = $class . '->' . $method; + } + + $result = $this->app->invoke($call, [$params]); + + return $result; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/thinkphp/library/think/Lang.php b/thinkphp/library/think/Lang.php new file mode 100644 index 0000000..be7979f --- /dev/null +++ b/thinkphp/library/think/Lang.php @@ -0,0 +1,284 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Lang +{ + /** + * 多语言信息 + * @var array + */ + private $lang = []; + + /** + * 当前语言 + * @var string + */ + private $range = 'zh-cn'; + + /** + * 多语言自动侦测变量名 + * @var string + */ + protected $langDetectVar = 'lang'; + + /** + * 多语言cookie变量 + * @var string + */ + protected $langCookieVar = 'think_var'; + + /** + * 允许的多语言列表 + * @var array + */ + protected $allowLangList = []; + + /** + * Accept-Language转义为对应语言包名称 系统默认配置 + * @var string + */ + protected $acceptLanguage = [ + 'zh-hans-cn' => 'zh-cn', + ]; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + // 设定当前的语言 + public function range($range = '') + { + if ('' == $range) { + return $this->range; + } else { + $this->range = $range; + } + } + + /** + * 设置语言定义(不区分大小写) + * @access public + * @param string|array $name 语言变量 + * @param string $value 语言值 + * @param string $range 语言作用域 + * @return mixed + */ + public function set($name, $value = null, $range = '') + { + $range = $range ?: $this->range; + // 批量定义 + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + if (is_array($name)) { + return $this->lang[$range] = array_change_key_case($name) + $this->lang[$range]; + } + + return $this->lang[$range][strtolower($name)] = $value; + } + + /** + * 加载语言定义(不区分大小写) + * @access public + * @param string|array $file 语言文件 + * @param string $range 语言作用域 + * @return array + */ + public function load($file, $range = '') + { + $range = $range ?: $this->range; + if (!isset($this->lang[$range])) { + $this->lang[$range] = []; + } + + // 批量定义 + if (is_string($file)) { + $file = [$file]; + } + + $lang = []; + + foreach ($file as $_file) { + if (is_file($_file)) { + // 记录加载信息 + $this->app->log('[ LANG ] ' . $_file); + $_lang = include $_file; + if (is_array($_lang)) { + $lang = array_change_key_case($_lang) + $lang; + } + } + } + + if (!empty($lang)) { + $this->lang[$range] = $lang + $this->lang[$range]; + } + + return $this->lang[$range]; + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param string $range 语言作用域 + * @return bool + */ + public function has($name, $range = '') + { + $range = $range ?: $this->range; + + return isset($this->lang[$range][strtolower($name)]); + } + + /** + * 获取语言定义(不区分大小写) + * @access public + * @param string|null $name 语言变量 + * @param array $vars 变量替换 + * @param string $range 语言作用域 + * @return mixed + */ + public function get($name = null, $vars = [], $range = '') + { + $range = $range ?: $this->range; + + // 空参数返回所有定义 + if (is_null($name)) { + return $this->lang[$range]; + } + + $key = strtolower($name); + $value = isset($this->lang[$range][$key]) ? $this->lang[$range][$key] : $name; + + // 变量解析 + if (!empty($vars) && is_array($vars)) { + /** + * Notes: + * 为了检测的方便,数字索引的判断仅仅是参数数组的第一个元素的key为数字0 + * 数字索引采用的是系统的 sprintf 函数替换,用法请参考 sprintf 函数 + */ + if (key($vars) === 0) { + // 数字索引解析 + array_unshift($vars, $value); + $value = call_user_func_array('sprintf', $vars); + } else { + // 关联索引解析 + $replace = array_keys($vars); + foreach ($replace as &$v) { + $v = "{:{$v}}"; + } + $value = str_replace($replace, $vars, $value); + } + } + + return $value; + } + + /** + * 自动侦测设置获取语言选择 + * @access public + * @return string + */ + public function detect() + { + // 自动侦测设置获取语言选择 + $langSet = ''; + + if (isset($_GET[$this->langDetectVar])) { + // url中设置了语言变量 + $langSet = strtolower($_GET[$this->langDetectVar]); + } elseif (isset($_COOKIE[$this->langCookieVar])) { + // Cookie中设置了语言变量 + $langSet = strtolower($_COOKIE[$this->langCookieVar]); + } elseif (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { + // 自动侦测浏览器语言 + preg_match('/^([a-z\d\-]+)/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $matches); + $langSet = strtolower($matches[1]); + if (isset($this->acceptLanguage[$langSet])) { + $langSet = $this->acceptLanguage[$langSet]; + } + } + + if (empty($this->allowLangList) || in_array($langSet, $this->allowLangList)) { + // 合法的语言 + $this->range = $langSet ?: $this->range; + } + + return $this->range; + } + + /** + * 设置当前语言到Cookie + * @access public + * @param string $lang 语言 + * @return void + */ + public function saveToCookie($lang = null) + { + $range = $lang ?: $this->range; + + $_COOKIE[$this->langCookieVar] = $range; + } + + /** + * 设置语言自动侦测的变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public function setLangDetectVar($var) + { + $this->langDetectVar = $var; + } + + /** + * 设置语言的cookie保存变量 + * @access public + * @param string $var 变量名称 + * @return void + */ + public function setLangCookieVar($var) + { + $this->langCookieVar = $var; + } + + /** + * 设置允许的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public function setAllowLangList(array $list) + { + $this->allowLangList = $list; + } + + /** + * 设置转义的语言列表 + * @access public + * @param array $list 语言列表 + * @return void + */ + public function setAcceptLanguage(array $list) + { + $this->acceptLanguage = array_merge($this->acceptLanguage, $list); + } +} diff --git a/thinkphp/library/think/Loader.php b/thinkphp/library/think/Loader.php new file mode 100644 index 0000000..d807db6 --- /dev/null +++ b/thinkphp/library/think/Loader.php @@ -0,0 +1,417 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Loader +{ + /** + * 类名映射信息 + * @var array + */ + protected static $classMap = []; + + /** + * 类库别名 + * @var array + */ + protected static $classAlias = []; + + /** + * PSR-4 + * @var array + */ + private static $prefixLengthsPsr4 = []; + private static $prefixDirsPsr4 = []; + private static $fallbackDirsPsr4 = []; + + /** + * PSR-0 + * @var array + */ + private static $prefixesPsr0 = []; + private static $fallbackDirsPsr0 = []; + + /** + * 需要加载的文件 + * @var array + */ + private static $files = []; + + /** + * Composer安装路径 + * @var string + */ + private static $composerPath; + + // 获取应用根目录 + public static function getRootPath() + { + if ('cli' == PHP_SAPI) { + $scriptName = realpath($_SERVER['argv'][0]); + } else { + $scriptName = $_SERVER['SCRIPT_FILENAME']; + } + + $path = realpath(dirname($scriptName)); + + if (!is_file($path . DIRECTORY_SEPARATOR . 'think')) { + $path = dirname($path); + } + + return $path . DIRECTORY_SEPARATOR; + } + + // 注册自动加载机制 + public static function register($autoload = '') + { + // 注册系统自动加载 + spl_autoload_register($autoload ?: 'think\\Loader::autoload', true, true); + + $rootPath = self::getRootPath(); + + self::$composerPath = $rootPath . 'vendor' . DIRECTORY_SEPARATOR . 'composer' . DIRECTORY_SEPARATOR; + + // Composer自动加载支持 + if (is_dir(self::$composerPath)) { + if (is_file(self::$composerPath . 'autoload_static.php')) { + require self::$composerPath . 'autoload_static.php'; + + $declaredClass = get_declared_classes(); + $composerClass = array_pop($declaredClass); + + foreach (['prefixLengthsPsr4', 'prefixDirsPsr4', 'fallbackDirsPsr4', 'prefixesPsr0', 'fallbackDirsPsr0', 'classMap', 'files'] as $attr) { + if (property_exists($composerClass, $attr)) { + self::${$attr} = $composerClass::${$attr}; + } + } + } else { + self::registerComposerLoader(self::$composerPath); + } + } + + // 注册命名空间定义 + self::addNamespace([ + 'think' => __DIR__, + 'traits' => dirname(__DIR__) . DIRECTORY_SEPARATOR . 'traits', + ]); + + // 加载类库映射文件 + if (is_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')) { + self::addClassMap(__include_file($rootPath . 'runtime' . DIRECTORY_SEPARATOR . 'classmap.php')); + } + + // 自动加载extend目录 + self::addAutoLoadDir($rootPath . 'extend'); + } + + // 自动加载 + public static function autoload($class) + { + if (isset(self::$classAlias[$class])) { + return class_alias(self::$classAlias[$class], $class); + } + + if ($file = self::findFile($class)) { + + // Win环境严格区分大小写 + if (strpos(PHP_OS, 'WIN') !== false && pathinfo($file, PATHINFO_FILENAME) != pathinfo(realpath($file), PATHINFO_FILENAME)) { + return false; + } + + __include_file($file); + return true; + } + } + + /** + * 查找文件 + * @access private + * @param string $class + * @return string|false + */ + private static function findFile($class) + { + if (!empty(self::$classMap[$class])) { + // 类库映射 + return self::$classMap[$class]; + } + + // 查找 PSR-4 + $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php'; + + $first = $class[0]; + if (isset(self::$prefixLengthsPsr4[$first])) { + foreach (self::$prefixLengthsPsr4[$first] as $prefix => $length) { + if (0 === strpos($class, $prefix)) { + foreach (self::$prefixDirsPsr4[$prefix] as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, $length))) { + return $file; + } + } + } + } + } + + // 查找 PSR-4 fallback dirs + foreach (self::$fallbackDirsPsr4 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) { + return $file; + } + } + + // 查找 PSR-0 + if (false !== $pos = strrpos($class, '\\')) { + // namespaced class name + $logicalPathPsr0 = substr($logicalPathPsr4, 0, $pos + 1) + . strtr(substr($logicalPathPsr4, $pos + 1), '_', DIRECTORY_SEPARATOR); + } else { + // PEAR-like class name + $logicalPathPsr0 = strtr($class, '_', DIRECTORY_SEPARATOR) . '.php'; + } + + if (isset(self::$prefixesPsr0[$first])) { + foreach (self::$prefixesPsr0[$first] as $prefix => $dirs) { + if (0 === strpos($class, $prefix)) { + foreach ($dirs as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + } + } + } + + // 查找 PSR-0 fallback dirs + foreach (self::$fallbackDirsPsr0 as $dir) { + if (is_file($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr0)) { + return $file; + } + } + + return self::$classMap[$class] = false; + } + + // 注册classmap + public static function addClassMap($class, $map = '') + { + if (is_array($class)) { + self::$classMap = array_merge(self::$classMap, $class); + } else { + self::$classMap[$class] = $map; + } + } + + // 注册命名空间 + public static function addNamespace($namespace, $path = '') + { + if (is_array($namespace)) { + foreach ($namespace as $prefix => $paths) { + self::addPsr4($prefix . '\\', rtrim($paths, DIRECTORY_SEPARATOR), true); + } + } else { + self::addPsr4($namespace . '\\', rtrim($path, DIRECTORY_SEPARATOR), true); + } + } + + // 添加Ps0空间 + private static function addPsr0($prefix, $paths, $prepend = false) + { + if (!$prefix) { + if ($prepend) { + self::$fallbackDirsPsr0 = array_merge( + (array) $paths, + self::$fallbackDirsPsr0 + ); + } else { + self::$fallbackDirsPsr0 = array_merge( + self::$fallbackDirsPsr0, + (array) $paths + ); + } + + return; + } + + $first = $prefix[0]; + if (!isset(self::$prefixesPsr0[$first][$prefix])) { + self::$prefixesPsr0[$first][$prefix] = (array) $paths; + + return; + } + + if ($prepend) { + self::$prefixesPsr0[$first][$prefix] = array_merge( + (array) $paths, + self::$prefixesPsr0[$first][$prefix] + ); + } else { + self::$prefixesPsr0[$first][$prefix] = array_merge( + self::$prefixesPsr0[$first][$prefix], + (array) $paths + ); + } + } + + // 添加Psr4空间 + private static function addPsr4($prefix, $paths, $prepend = false) + { + if (!$prefix) { + // Register directories for the root namespace. + if ($prepend) { + self::$fallbackDirsPsr4 = array_merge( + (array) $paths, + self::$fallbackDirsPsr4 + ); + } else { + self::$fallbackDirsPsr4 = array_merge( + self::$fallbackDirsPsr4, + (array) $paths + ); + } + } elseif (!isset(self::$prefixDirsPsr4[$prefix])) { + // Register directories for a new namespace. + $length = strlen($prefix); + if ('\\' !== $prefix[$length - 1]) { + throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator."); + } + + self::$prefixLengthsPsr4[$prefix[0]][$prefix] = $length; + self::$prefixDirsPsr4[$prefix] = (array) $paths; + } elseif ($prepend) { + // Prepend directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + (array) $paths, + self::$prefixDirsPsr4[$prefix] + ); + } else { + // Append directories for an already registered namespace. + self::$prefixDirsPsr4[$prefix] = array_merge( + self::$prefixDirsPsr4[$prefix], + (array) $paths + ); + } + } + + // 注册自动加载类库目录 + public static function addAutoLoadDir($path) + { + self::$fallbackDirsPsr4[] = $path; + } + + // 注册类别名 + public static function addClassAlias($alias, $class = null) + { + if (is_array($alias)) { + self::$classAlias = array_merge(self::$classAlias, $alias); + } else { + self::$classAlias[$alias] = $class; + } + } + + // 注册composer自动加载 + public static function registerComposerLoader($composerPath) + { + if (is_file($composerPath . 'autoload_namespaces.php')) { + $map = require $composerPath . 'autoload_namespaces.php'; + foreach ($map as $namespace => $path) { + self::addPsr0($namespace, $path); + } + } + + if (is_file($composerPath . 'autoload_psr4.php')) { + $map = require $composerPath . 'autoload_psr4.php'; + foreach ($map as $namespace => $path) { + self::addPsr4($namespace, $path); + } + } + + if (is_file($composerPath . 'autoload_classmap.php')) { + $classMap = require $composerPath . 'autoload_classmap.php'; + if ($classMap) { + self::addClassMap($classMap); + } + } + + if (is_file($composerPath . 'autoload_files.php')) { + self::$files = require $composerPath . 'autoload_files.php'; + } + } + + // 加载composer autofile文件 + public static function loadComposerAutoloadFiles() + { + foreach (self::$files as $fileIdentifier => $file) { + if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { + __require_file($file); + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; + } + } + } + + /** + * 字符串命名风格转换 + * type 0 将Java风格转换为C的风格 1 将C风格转换为Java的风格 + * @access public + * @param string $name 字符串 + * @param integer $type 转换类型 + * @param bool $ucfirst 首字母是否大写(驼峰规则) + * @return string + */ + public static function parseName($name, $type = 0, $ucfirst = true) + { + if ($type) { + $name = preg_replace_callback('/_([a-zA-Z])/', function ($match) { + return strtoupper($match[1]); + }, $name); + return $ucfirst ? ucfirst($name) : lcfirst($name); + } + + return strtolower(trim(preg_replace("/[A-Z]/", "_\\0", $name), "_")); + } + + /** + * 创建工厂对象实例 + * @access public + * @param string $name 工厂类名 + * @param string $namespace 默认命名空间 + * @return mixed + */ + public static function factory($name, $namespace = '', ...$args) + { + $class = false !== strpos($name, '\\') ? $name : $namespace . ucwords($name); + + if (class_exists($class)) { + return Container::getInstance()->invokeClass($class, $args); + } else { + throw new ClassNotFoundException('class not exists:' . $class, $class); + } + } +} + +/** + * 作用范围隔离 + * + * @param $file + * @return mixed + */ +function __include_file($file) +{ + return include $file; +} + +function __require_file($file) +{ + return require $file; +} diff --git a/thinkphp/library/think/Log.php b/thinkphp/library/think/Log.php new file mode 100644 index 0000000..1a37496 --- /dev/null +++ b/thinkphp/library/think/Log.php @@ -0,0 +1,387 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Log implements LoggerInterface +{ + const EMERGENCY = 'emergency'; + const ALERT = 'alert'; + const CRITICAL = 'critical'; + const ERROR = 'error'; + const WARNING = 'warning'; + const NOTICE = 'notice'; + const INFO = 'info'; + const DEBUG = 'debug'; + const SQL = 'sql'; + + /** + * 日志信息 + * @var array + */ + protected $log = []; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 日志写入驱动 + * @var object + */ + protected $driver; + + /** + * 日志授权key + * @var string + */ + protected $key; + + /** + * 是否允许日志写入 + * @var bool + */ + protected $allowWrite = true; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app) + { + $this->app = $app; + } + + public static function __make(App $app, Config $config) + { + return (new static($app))->init($config->pull('log')); + } + + /** + * 日志初始化 + * @access public + * @param array $config + * @return $this + */ + public function init($config = []) + { + $type = isset($config['type']) ? $config['type'] : 'File'; + + $this->config = $config; + + unset($config['type']); + + if (!empty($config['close'])) { + $this->allowWrite = false; + } + + $this->driver = Loader::factory($type, '\\think\\log\\driver\\', $config); + + return $this; + } + + /** + * 获取日志信息 + * @access public + * @param string $type 信息类型 + * @return array + */ + public function getLog($type = '') + { + return $type ? $this->log[$type] : $this->log; + } + + /** + * 记录日志信息 + * @access public + * @param mixed $msg 日志信息 + * @param string $type 日志级别 + * @param array $context 替换内容 + * @return $this + */ + public function record($msg, $type = 'info', array $context = []) + { + if (!$this->allowWrite) { + return; + } + + if (is_string($msg) && !empty($context)) { + $replace = []; + foreach ($context as $key => $val) { + $replace['{' . $key . '}'] = $val; + } + + $msg = strtr($msg, $replace); + } + + if (PHP_SAPI == 'cli') { + // 命令行日志实时写入 + $this->write($msg, $type, true); + } else { + $this->log[$type][] = $msg; + } + + return $this; + } + + /** + * 清空日志信息 + * @access public + * @return $this + */ + public function clear() + { + $this->log = []; + + return $this; + } + + /** + * 当前日志记录的授权key + * @access public + * @param string $key 授权key + * @return $this + */ + public function key($key) + { + $this->key = $key; + + return $this; + } + + /** + * 检查日志写入权限 + * @access public + * @param array $config 当前日志配置参数 + * @return bool + */ + public function check($config) + { + if ($this->key && !empty($config['allow_key']) && !in_array($this->key, $config['allow_key'])) { + return false; + } + + return true; + } + + /** + * 关闭本次请求日志写入 + * @access public + * @return $this + */ + public function close() + { + $this->allowWrite = false; + $this->log = []; + + return $this; + } + + /** + * 保存调试信息 + * @access public + * @return bool + */ + public function save() + { + if (empty($this->log) || !$this->allowWrite) { + return true; + } + + if (!$this->check($this->config)) { + // 检测日志写入权限 + return false; + } + + $log = []; + + foreach ($this->log as $level => $info) { + if (!$this->app->isDebug() && 'debug' == $level) { + continue; + } + + if (empty($this->config['level']) || in_array($level, $this->config['level'])) { + $log[$level] = $info; + + $this->app['hook']->listen('log_level', [$level, $info]); + } + } + + $result = $this->driver->save($log, true); + + if ($result) { + $this->log = []; + } + + return $result; + } + + /** + * 实时写入日志信息 并支持行为 + * @access public + * @param mixed $msg 调试信息 + * @param string $type 日志级别 + * @param bool $force 是否强制写入 + * @return bool + */ + public function write($msg, $type = 'info', $force = false) + { + // 封装日志信息 + if (empty($this->config['level'])) { + $force = true; + } + + if (true === $force || in_array($type, $this->config['level'])) { + $log[$type][] = $msg; + } else { + return false; + } + + // 监听log_write + $this->app['hook']->listen('log_write', $log); + + // 写入日志 + return $this->driver->save($log, false); + } + + /** + * 记录日志信息 + * @access public + * @param string $level 日志级别 + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function log($level, $message, array $context = []) + { + $this->record($message, $level, $context); + } + + /** + * 记录emergency信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function emergency($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录警报信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function alert($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录紧急情况 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function critical($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录错误信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function error($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录warning信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function warning($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录notice信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function notice($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录一般信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function info($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录调试信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function debug($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + /** + * 记录sql信息 + * @access public + * @param mixed $message 日志信息 + * @param array $context 替换内容 + * @return void + */ + public function sql($message, array $context = []) + { + $this->log(__FUNCTION__, $message, $context); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/thinkphp/library/think/Middleware.php b/thinkphp/library/think/Middleware.php new file mode 100644 index 0000000..d3f4360 --- /dev/null +++ b/thinkphp/library/think/Middleware.php @@ -0,0 +1,205 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use InvalidArgumentException; +use LogicException; +use think\exception\HttpResponseException; + +class Middleware +{ + protected $queue = []; + protected $app; + protected $config = [ + 'default_namespace' => 'app\\http\\middleware\\', + ]; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, $config); + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('middleware')); + } + + public function setConfig(array $config) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 导入中间件 + * @access public + * @param array $middlewares + * @param string $type 中间件类型 + */ + public function import(array $middlewares = [], $type = 'route') + { + foreach ($middlewares as $middleware) { + $this->add($middleware, $type); + } + } + + /** + * 注册中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function add($middleware, $type = 'route') + { + if (is_null($middleware)) { + return; + } + + $middleware = $this->buildMiddleware($middleware, $type); + + if ($middleware) { + $this->queue[$type][] = $middleware; + } + } + + /** + * 注册控制器中间件 + * @access public + * @param mixed $middleware + */ + public function controller($middleware) + { + return $this->add($middleware, 'controller'); + } + + /** + * 移除中间件 + * @access public + * @param mixed $middleware + * @param string $type 中间件类型 + */ + public function unshift($middleware, $type = 'route') + { + if (is_null($middleware)) { + return; + } + + $middleware = $this->buildMiddleware($middleware, $type); + + if ($middleware) { + array_unshift($this->queue[$type], $middleware); + } + } + + /** + * 获取注册的中间件 + * @access public + * @param string $type 中间件类型 + */ + public function all($type = 'route') + { + return $this->queue[$type] ?: []; + } + + /** + * 清除中间件 + * @access public + */ + public function clear() + { + $this->queue = []; + } + + /** + * 中间件调度 + * @access public + * @param Request $request + * @param string $type 中间件类型 + */ + public function dispatch(Request $request, $type = 'route') + { + return call_user_func($this->resolve($type), $request); + } + + /** + * 解析中间件 + * @access protected + * @param mixed $middleware + * @param string $type 中间件类型 + */ + protected function buildMiddleware($middleware, $type = 'route') + { + if (is_array($middleware)) { + list($middleware, $param) = $middleware; + } + + if ($middleware instanceof \Closure) { + return [$middleware, isset($param) ? $param : null]; + } + + if (!is_string($middleware)) { + throw new InvalidArgumentException('The middleware is invalid'); + } + + if (false === strpos($middleware, '\\')) { + if (isset($this->config[$middleware])) { + $middleware = $this->config[$middleware]; + } else { + $middleware = $this->config['default_namespace'] . $middleware; + } + } + + if (is_array($middleware)) { + return $this->import($middleware, $type); + } + + if (strpos($middleware, ':')) { + list($middleware, $param) = explode(':', $middleware, 2); + } + + return [[$this->app->make($middleware), 'handle'], isset($param) ? $param : null]; + } + + protected function resolve($type = 'route') + { + return function (Request $request) use ($type) { + + $middleware = array_shift($this->queue[$type]); + + if (null === $middleware) { + throw new InvalidArgumentException('The queue was exhausted, with no response returned'); + } + + list($call, $param) = $middleware; + + try { + $response = call_user_func_array($call, [$request, $this->resolve($type), $param]); + } catch (HttpResponseException $exception) { + $response = $exception->getResponse(); + } + + if (!$response instanceof Response) { + throw new LogicException('The middleware must return Response instance'); + } + + return $response; + }; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/thinkphp/library/think/Model.php b/thinkphp/library/think/Model.php new file mode 100644 index 0000000..e0dcecf --- /dev/null +++ b/thinkphp/library/think/Model.php @@ -0,0 +1,1071 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use InvalidArgumentException; +use think\db\Query; + +/** + * Class Model + * @package think + * @mixin Query + * @method \think\Model withAttr(array $name,\Closure $closure) 动态定义获取器 + */ +abstract class Model implements \JsonSerializable, \ArrayAccess +{ + use model\concern\Attribute; + use model\concern\RelationShip; + use model\concern\ModelEvent; + use model\concern\TimeStamp; + use model\concern\Conversion; + + /** + * 是否存在数据 + * @var bool + */ + private $exists = false; + + /** + * 是否Replace + * @var bool + */ + private $replace = false; + + /** + * 是否强制更新所有数据 + * @var bool + */ + private $force = false; + + /** + * 更新条件 + * @var array + */ + private $updateWhere; + + /** + * 数据库配置信息 + * @var array|string + */ + protected $connection = []; + + /** + * 数据库查询对象类名 + * @var string + */ + protected $query; + + /** + * 模型名称 + * @var string + */ + protected $name; + + /** + * 数据表名称 + * @var string + */ + protected $table; + + /** + * 写入自动完成定义 + * @var array + */ + protected $auto = []; + + /** + * 新增自动完成定义 + * @var array + */ + protected $insert = []; + + /** + * 更新自动完成定义 + * @var array + */ + protected $update = []; + + /** + * 初始化过的模型. + * @var array + */ + protected static $initialized = []; + + /** + * 是否从主库读取(主从分布式有效) + * @var array + */ + protected static $readMaster; + + /** + * 查询对象实例 + * @var Query + */ + protected $queryInstance; + + /** + * 错误信息 + * @var mixed + */ + protected $error; + + /** + * 软删除字段默认值 + * @var mixed + */ + protected $defaultSoftDelete; + + /** + * 全局查询范围 + * @var array + */ + protected $globalScope = []; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + */ + public function __construct($data = []) + { + if (is_object($data)) { + $this->data = get_object_vars($data); + } else { + $this->data = $data; + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $this->data)) { + unset($this->data[$key]); + } + } + } + + // 记录原始数据 + $this->origin = $this->data; + + $config = Db::getConfig(); + + if (empty($this->name)) { + // 当前模型名 + $name = str_replace('\\', '/', static::class); + $this->name = basename($name); + if (Container::get('config')->get('class_suffix')) { + $suffix = basename(dirname($name)); + $this->name = substr($this->name, 0, -strlen($suffix)); + } + } + + if (is_null($this->autoWriteTimestamp)) { + // 自动写入时间戳 + $this->autoWriteTimestamp = $config['auto_timestamp']; + } + + if (is_null($this->dateFormat)) { + // 设置时间戳格式 + $this->dateFormat = $config['datetime_format']; + } + + if (is_null($this->resultSetType)) { + $this->resultSetType = $config['resultset_type']; + } + + if (!empty($this->connection) && is_array($this->connection)) { + // 设置模型的数据库连接 + $this->connection = array_merge($config, $this->connection); + } + + if ($this->observerClass) { + // 注册模型观察者 + static::observe($this->observerClass); + } + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 获取当前模型名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否从主库读取数据(主从分布有效) + * @access public + * @param bool $all 是否所有模型有效 + * @return $this + */ + public function readMaster($all = false) + { + $model = $all ? '*' : static::class; + + static::$readMaster[$model] = true; + + return $this; + } + + /** + * 创建新的模型实例 + * @access public + * @param array|object $data 数据 + * @param bool $isUpdate 是否为更新 + * @param mixed $where 更新条件 + * @return Model + */ + public function newInstance($data = [], $isUpdate = false, $where = null) + { + return (new static($data))->isUpdate($isUpdate, $where); + } + + /** + * 创建模型的查询对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + // 设置当前模型 确保查询返回模型对象 + $query = Db::connect($this->connection, false, $this->query); + $query->model($this) + ->name($this->name) + ->json($this->json, $this->jsonAssoc) + ->setJsonFieldType($this->jsonType); + + if (isset(static::$readMaster['*']) || isset(static::$readMaster[static::class])) { + $query->master(true); + } + + // 设置当前数据表和模型名 + if (!empty($this->table)) { + $query->table($this->table); + } + + if (!empty($this->pk)) { + $query->pk($this->pk); + } + + return $query; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param Query $query 查询对象实例 + * @return $this + */ + public function setQuery($query) + { + $this->queryInstance = $query; + return $this; + } + + /** + * 获取当前模型的数据库查询对象 + * @access public + * @param bool|array $useBaseQuery 是否调用全局查询范围(或者指定查询范围名称) + * @return Query + */ + public function db($useBaseQuery = true) + { + if ($this->queryInstance) { + return $this->queryInstance; + } + + $query = $this->buildQuery(); + + // 软删除 + if (property_exists($this, 'withTrashed') && !$this->withTrashed) { + $this->withNoTrashed($query); + } + + // 全局作用域 + if (true === $useBaseQuery && method_exists($this, 'base')) { + call_user_func_array([$this, 'base'], [ & $query]); + } + + $globalScope = is_array($useBaseQuery) && $useBaseQuery ? $useBaseQuery : $this->globalScope; + + if ($globalScope && false !== $useBaseQuery) { + $query->scope($globalScope); + } + + // 返回当前模型的数据库查询对象 + return $query; + } + + /** + * 初始化模型 + * @access protected + * @return void + */ + protected function initialize() + { + if (!isset(static::$initialized[static::class])) { + static::$initialized[static::class] = true; + static::init(); + } + } + + /** + * 初始化处理 + * @access protected + * @return void + */ + protected static function init() + {} + + /** + * 数据自动完成 + * @access protected + * @param array $auto 要自动更新的字段列表 + * @return void + */ + protected function autoCompleteData($auto = []) + { + foreach ($auto as $field => $value) { + if (is_integer($field)) { + $field = $value; + $value = null; + } + + if (!isset($this->data[$field])) { + $default = null; + } else { + $default = $this->data[$field]; + } + + $this->setAttr($field, !is_null($value) ? $value : $default); + } + } + + /** + * 更新是否强制写入数据 而不做比较 + * @access public + * @param bool $force + * @return $this + */ + public function force($force = true) + { + $this->force = $force; + return $this; + } + + /** + * 判断force + * @access public + * @return bool + */ + public function isForce() + { + return $this->force; + } + + /** + * 新增数据是否使用Replace + * @access public + * @param bool $replace + * @return $this + */ + public function replace($replace = true) + { + $this->replace = $replace; + return $this; + } + + /** + * 设置数据是否存在 + * @access public + * @param bool $exists + * @return void + */ + public function exists($exists) + { + $this->exists = $exists; + } + + /** + * 判断数据是否存在数据库 + * @access public + * @return bool + */ + public function isExists() + { + return $this->exists; + } + + /** + * 保存当前数据对象 + * @access public + * @param array $data 数据 + * @param array $where 更新条件 + * @param string $sequence 自增序列名 + * @return bool + */ + public function save($data = [], $where = [], $sequence = null) + { + if (is_string($data)) { + $sequence = $data; + $data = []; + } + + if (!$this->checkBeforeSave($data, $where)) { + return false; + } + + $result = $this->exists ? $this->updateData($where) : $this->insertData($sequence); + + if (false === $result) { + return false; + } + + // 写入回调 + $this->trigger('after_write'); + + // 重新记录原始数据 + $this->origin = $this->data; + $this->set = []; + + return true; + } + + /** + * 写入之前检查数据 + * @access protected + * @param array $data 数据 + * @param array $where 保存条件 + * @return bool + */ + protected function checkBeforeSave($data, $where) + { + if (!empty($data)) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + + if (!empty($where)) { + $this->exists = true; + $this->updateWhere = $where; + } + } + + // 数据自动完成 + $this->autoCompleteData($this->auto); + + // 事件回调 + if (false === $this->trigger('before_write')) { + return false; + } + + return true; + } + + /** + * 检查数据是否允许写入 + * @access protected + * @param array $append 自动完成的字段列表 + * @return array + */ + protected function checkAllowFields(array $append = []) + { + // 检测字段 + if (empty($this->field) || true === $this->field) { + $query = $this->db(false); + $table = $this->table ?: $query->getTable(); + + $this->field = $query->getConnection()->getTableFields($table); + + $field = $this->field; + } else { + $field = array_merge($this->field, $append); + + if ($this->autoWriteTimestamp) { + array_push($field, $this->createTime, $this->updateTime); + } + } + + if ($this->disuse) { + // 废弃字段 + $field = array_diff($field, (array) $this->disuse); + } + + return $field; + } + + /** + * 更新写入数据 + * @access protected + * @param mixed $where 更新条件 + * @return bool + */ + protected function updateData($where) + { + // 自动更新 + $this->autoCompleteData($this->update); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + // 获取有更新的数据 + $data = $this->getChangedData(); + + if (empty($data)) { + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + return false; + } elseif ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])) { + // 自动写入更新时间 + $data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + + $this->data[$this->updateTime] = $data[$this->updateTime]; + } + + if (empty($where) && !empty($this->updateWhere)) { + $where = $this->updateWhere; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->update)); + + // 保留主键数据 + foreach ($this->data as $key => $val) { + if ($this->isPk($key)) { + $data[$key] = $val; + } + } + + $pk = $this->getPk(); + $array = []; + + foreach ((array) $pk as $key) { + if (isset($data[$key])) { + $array[] = [$key, '=', $data[$key]]; + unset($data[$key]); + } + } + + if (!empty($array)) { + $where = $array; + } + + foreach ((array) $this->relationWrite as $name => $val) { + if (is_array($val)) { + foreach ($val as $key) { + if (isset($data[$key])) { + unset($data[$key]); + } + } + } + } + + // 模型更新 + $db = $this->db(false); + $db->startTrans(); + + try { + $db->where($where) + ->strict(false) + ->field($allowFields) + ->update($data); + + // 关联更新 + if (!empty($this->relationWrite)) { + $this->autoRelationUpdate(); + } + + $db->commit(); + + // 更新回调 + $this->trigger('after_update'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 新增写入数据 + * @access protected + * @param string $sequence 自增序列名 + * @return bool + */ + protected function insertData($sequence) + { + // 自动写入 + $this->autoCompleteData($this->insert); + + // 时间戳自动写入 + $this->checkTimeStampWrite(); + + if (false === $this->trigger('before_insert')) { + return false; + } + + // 检查允许字段 + $allowFields = $this->checkAllowFields(array_merge($this->auto, $this->insert)); + + $db = $this->db(false); + $db->startTrans(); + + try { + $result = $db->strict(false) + ->field($allowFields) + ->insert($this->data, $this->replace, false, $sequence); + + // 获取自动增长主键 + if ($result && $insertId = $db->getLastInsID($sequence)) { + $pk = $this->getPk(); + + foreach ((array) $pk as $key) { + if (!isset($this->data[$key]) || '' == $this->data[$key]) { + $this->data[$key] = $insertId; + } + } + } + + // 关联写入 + if (!empty($this->relationWrite)) { + $this->autoRelationInsert(); + } + + $db->commit(); + + // 标记为更新 + $this->exists = true; + + // 新增回调 + $this->trigger('after_insert'); + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return bool + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + $result = $this->db(false) + ->where($where) + ->setInc($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] += $step; + } + + // 更新回调 + $this->trigger('after_update'); + + return true; + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return bool + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + // 读取更新条件 + $where = $this->getWhere(); + + // 事件回调 + if (false === $this->trigger('before_update')) { + return false; + } + + $result = $this->db(false) + ->where($where) + ->setDec($field, $step, $lazyTime); + + if (true !== $result) { + $this->data[$field] -= $step; + } + + // 更新回调 + $this->trigger('after_update'); + + return true; + } + + /** + * 获取当前的更新条件 + * @access protected + * @return mixed + */ + protected function getWhere() + { + // 删除条件 + $pk = $this->getPk(); + + if (is_string($pk) && isset($this->data[$pk])) { + $where[] = [$pk, '=', $this->data[$pk]]; + } elseif (!empty($this->updateWhere)) { + $where = $this->updateWhere; + } else { + $where = null; + } + + return $where; + } + + /** + * 保存多个数据到当前数据对象 + * @access public + * @param array $dataSet 数据 + * @param boolean $replace 是否自动识别更新和写入 + * @return Collection + * @throws \Exception + */ + public function saveAll($dataSet, $replace = true) + { + $db = $this->db(false); + $db->startTrans(); + + try { + $pk = $this->getPk(); + + if (is_string($pk) && $replace) { + $auto = true; + } + + $result = []; + + foreach ($dataSet as $key => $data) { + if ($this->exists || (!empty($auto) && isset($data[$pk]))) { + $result[$key] = self::update($data, [], $this->field); + } else { + $result[$key] = self::create($data, $this->field, $this->replace); + } + } + + $db->commit(); + + return $this->toCollection($result); + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 是否为更新数据 + * @access public + * @param mixed $update + * @param mixed $where + * @return $this + */ + public function isUpdate($update = true, $where = null) + { + if (is_bool($update)) { + $this->exists = $update; + + if (!empty($where)) { + $this->updateWhere = $where; + } + } else { + $this->exists = true; + $this->updateWhere = $update; + } + + return $this; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete() + { + if (!$this->exists || false === $this->trigger('before_delete')) { + return false; + } + + // 读取更新条件 + $where = $this->getWhere(); + + $db = $this->db(false); + $db->startTrans(); + + try { + // 删除当前模型数据 + $db->where($where)->delete(); + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $db->commit(); + + $this->trigger('after_delete'); + + $this->exists = false; + + return true; + } catch (\Exception $e) { + $db->rollback(); + throw $e; + } + } + + /** + * 设置自动完成的字段( 规则通过修改器定义) + * @access public + * @param array $fields 需要自动完成的字段 + * @return $this + */ + public function auto($fields) + { + $this->auto = $fields; + + return $this; + } + + /** + * 写入数据 + * @access public + * @param array $data 数据数组 + * @param array|true $field 允许字段 + * @param bool $replace 使用Replace + * @return static + */ + public static function create($data = [], $field = null, $replace = false) + { + $model = new static(); + + if (!empty($field)) { + $model->allowField($field); + } + + $model->isUpdate(false)->replace($replace)->save($data, []); + + return $model; + } + + /** + * 更新数据 + * @access public + * @param array $data 数据数组 + * @param array $where 更新条件 + * @param array|true $field 允许字段 + * @return static + */ + public static function update($data = [], $where = [], $field = null) + { + $model = new static(); + + if (!empty($field)) { + $model->allowField($field); + } + + $model->isUpdate(true)->save($data, $where); + + return $model; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @return bool + */ + public static function destroy($data) + { + if (empty($data) && 0 !== $data) { + return false; + } + + $model = new static(); + + $query = $model->db(); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($query); + $data = null; + } + + $resultSet = $query->select($data); + + if ($resultSet) { + foreach ($resultSet as $data) { + $data->delete(); + } + } + + return true; + } + + /** + * 获取错误信息 + * @access public + * @return mixed + */ + public function getError() + { + return $this->error; + } + + /** + * 解序列化后处理 + */ + public function __wakeup() + { + $this->initialize(); + } + + public function __debugInfo() + { + return [ + 'data' => $this->data, + 'relation' => $this->relation, + ]; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->setAttr($name, $value); + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return $this->getAttr($name); + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + try { + return !is_null($this->getAttr($name)); + } catch (InvalidArgumentException $e) { + return false; + } + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->data[$name], $this->relation[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->setAttr($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->getAttr($name); + } + + /** + * 设置是否使用全局查询范围 + * @access public + * @param bool|array $use 是否启用全局查询范围(或者用数组指定查询范围名称) + * @return Query + */ + public static function useGlobalScope($use) + { + $model = new static(); + + return $model->db($use); + } + + public function __call($method, $args) + { + if ('withattr' == strtolower($method)) { + return call_user_func_array([$this, 'withAttribute'], $args); + } + + return call_user_func_array([$this->db(), $method], $args); + } + + public static function __callStatic($method, $args) + { + $model = new static(); + + return call_user_func_array([$model->db(), $method], $args); + } +} diff --git a/thinkphp/library/think/Paginator.php b/thinkphp/library/think/Paginator.php new file mode 100644 index 0000000..bbe63e2 --- /dev/null +++ b/thinkphp/library/think/Paginator.php @@ -0,0 +1,445 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use ArrayAccess; +use ArrayIterator; +use Countable; +use IteratorAggregate; +use JsonSerializable; +use Traversable; + +abstract class Paginator implements ArrayAccess, Countable, IteratorAggregate, JsonSerializable +{ + /** + * 是否简洁模式 + * @var bool + */ + protected $simple = false; + + /** + * 数据集 + * @var Collection + */ + protected $items; + + /** + * 当前页 + * @var integer + */ + protected $currentPage; + + /** + * 最后一页 + * @var integer + */ + protected $lastPage; + + /** + * 数据总数 + * @var integer|null + */ + protected $total; + + /** + * 每页数量 + * @var integer + */ + protected $listRows; + + /** + * 是否有下一页 + * @var bool + */ + protected $hasMore; + + /** + * 分页配置 + * @var array + */ + protected $options = [ + 'var_page' => 'page', + 'path' => '/', + 'query' => [], + 'fragment' => '', + ]; + + public function __construct($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + $this->options = array_merge($this->options, $options); + + $this->options['path'] = '/' != $this->options['path'] ? rtrim($this->options['path'], '/') : $this->options['path']; + + $this->simple = $simple; + $this->listRows = $listRows; + + if (!$items instanceof Collection) { + $items = Collection::make($items); + } + + if ($simple) { + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = count($items) > ($this->listRows); + $items = $items->slice(0, $this->listRows); + } else { + $this->total = $total; + $this->lastPage = (int) ceil($total / $listRows); + $this->currentPage = $this->setCurrentPage($currentPage); + $this->hasMore = $this->currentPage < $this->lastPage; + } + $this->items = $items; + } + + /** + * @access public + * @param $items + * @param $listRows + * @param null $currentPage + * @param null $total + * @param bool $simple + * @param array $options + * @return Paginator + */ + public static function make($items, $listRows, $currentPage = null, $total = null, $simple = false, $options = []) + { + return new static($items, $listRows, $currentPage, $total, $simple, $options); + } + + protected function setCurrentPage($currentPage) + { + if (!$this->simple && $currentPage > $this->lastPage) { + return $this->lastPage > 0 ? $this->lastPage : 1; + } + + return $currentPage; + } + + /** + * 获取页码对应的链接 + * + * @access protected + * @param $page + * @return string + */ + protected function url($page) + { + if ($page <= 0) { + $page = 1; + } + + if (strpos($this->options['path'], '[PAGE]') === false) { + $parameters = [$this->options['var_page'] => $page]; + $path = $this->options['path']; + } else { + $parameters = []; + $path = str_replace('[PAGE]', $page, $this->options['path']); + } + + if (count($this->options['query']) > 0) { + $parameters = array_merge($this->options['query'], $parameters); + } + + $url = $path; + if (!empty($parameters)) { + $url .= '?' . http_build_query($parameters, null, '&'); + } + + return $url . $this->buildFragment(); + } + + /** + * 自动获取当前页码 + * @access public + * @param string $varPage + * @param int $default + * @return int + */ + public static function getCurrentPage($varPage = 'page', $default = 1) + { + $page = Container::get('request')->param($varPage); + + if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int) $page >= 1) { + return $page; + } + + return $default; + } + + /** + * 自动获取当前的path + * @access public + * @return string + */ + public static function getCurrentPath() + { + return Container::get('request')->baseUrl(); + } + + public function total() + { + if ($this->simple) { + throw new \DomainException('not support total'); + } + + return $this->total; + } + + public function listRows() + { + return $this->listRows; + } + + public function currentPage() + { + return $this->currentPage; + } + + public function lastPage() + { + if ($this->simple) { + throw new \DomainException('not support last'); + } + + return $this->lastPage; + } + + /** + * 数据是否足够分页 + * @access public + * @return boolean + */ + public function hasPages() + { + return !(1 == $this->currentPage && !$this->hasMore); + } + + /** + * 创建一组分页链接 + * + * @access public + * @param int $start + * @param int $end + * @return array + */ + public function getUrlRange($start, $end) + { + $urls = []; + + for ($page = $start; $page <= $end; $page++) { + $urls[$page] = $this->url($page); + } + + return $urls; + } + + /** + * 设置URL锚点 + * + * @access public + * @param string|null $fragment + * @return $this + */ + public function fragment($fragment) + { + $this->options['fragment'] = $fragment; + + return $this; + } + + /** + * 添加URL参数 + * + * @access public + * @param array|string $key + * @param string|null $value + * @return $this + */ + public function appends($key, $value = null) + { + if (!is_array($key)) { + $queries = [$key => $value]; + } else { + $queries = $key; + } + + foreach ($queries as $k => $v) { + if ($k !== $this->options['var_page']) { + $this->options['query'][$k] = $v; + } + } + + return $this; + } + + /** + * 构造锚点字符串 + * + * @access public + * @return string + */ + protected function buildFragment() + { + return $this->options['fragment'] ? '#' . $this->options['fragment'] : ''; + } + + /** + * 渲染分页html + * @access public + * @return mixed + */ + abstract public function render(); + + public function items() + { + return $this->items->all(); + } + + public function getCollection() + { + return $this->items; + } + + public function isEmpty() + { + return $this->items->isEmpty(); + } + + /** + * 给每个元素执行个回调 + * + * @access public + * @param callable $callback + * @return $this + */ + public function each(callable $callback) + { + foreach ($this->items as $key => $item) { + $result = $callback($item, $key); + + if (false === $result) { + break; + } elseif (!is_object($item)) { + $this->items[$key] = $result; + } + } + + return $this; + } + + /** + * Retrieve an external iterator + * @access public + * @return Traversable An instance of an object implementing Iterator or + * Traversable + */ + public function getIterator() + { + return new ArrayIterator($this->items->all()); + } + + /** + * Whether a offset exists + * @access public + * @param mixed $offset + * @return bool + */ + public function offsetExists($offset) + { + return $this->items->offsetExists($offset); + } + + /** + * Offset to retrieve + * @access public + * @param mixed $offset + * @return mixed + */ + public function offsetGet($offset) + { + return $this->items->offsetGet($offset); + } + + /** + * Offset to set + * @access public + * @param mixed $offset + * @param mixed $value + */ + public function offsetSet($offset, $value) + { + $this->items->offsetSet($offset, $value); + } + + /** + * Offset to unset + * @access public + * @param mixed $offset + * @return void + * @since 5.0.0 + */ + public function offsetUnset($offset) + { + $this->items->offsetUnset($offset); + } + + /** + * Count elements of an object + */ + public function count() + { + return $this->items->count(); + } + + public function __toString() + { + return (string) $this->render(); + } + + public function toArray() + { + try { + $total = $this->total(); + } catch (\DomainException $e) { + $total = null; + } + + return [ + 'total' => $total, + 'per_page' => $this->listRows(), + 'current_page' => $this->currentPage(), + 'last_page' => $this->lastPage, + 'data' => $this->items->toArray(), + ]; + } + + /** + * Specify data which should be serialized to JSON + */ + public function jsonSerialize() + { + return $this->toArray(); + } + + public function __call($name, $arguments) + { + $collection = $this->getCollection(); + + $result = call_user_func_array([$collection, $name], $arguments); + + if ($result === $collection) { + return $this; + } + + return $result; + } + +} diff --git a/thinkphp/library/think/Process.php b/thinkphp/library/think/Process.php new file mode 100644 index 0000000..3b574db --- /dev/null +++ b/thinkphp/library/think/Process.php @@ -0,0 +1,1268 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\process\exception\Failed as ProcessFailedException; +use think\process\exception\Timeout as ProcessTimeoutException; +use think\process\pipes\Pipes; +use think\process\pipes\Unix as UnixPipes; +use think\process\pipes\Windows as WindowsPipes; +use think\process\Utils; + +class Process +{ + + const ERR = 'err'; + const OUT = 'out'; + + const STATUS_READY = 'ready'; + const STATUS_STARTED = 'started'; + const STATUS_TERMINATED = 'terminated'; + + const STDIN = 0; + const STDOUT = 1; + const STDERR = 2; + + const TIMEOUT_PRECISION = 0.2; + + private $callback; + private $commandline; + private $cwd; + private $env; + private $input; + private $starttime; + private $lastOutputTime; + private $timeout; + private $idleTimeout; + private $options; + private $exitcode; + private $fallbackExitcode; + private $processInformation; + private $outputDisabled = false; + private $stdout; + private $stderr; + private $enhanceWindowsCompatibility = true; + private $enhanceSigchildCompatibility; + private $process; + private $status = self::STATUS_READY; + private $incrementalOutputOffset = 0; + private $incrementalErrorOutputOffset = 0; + private $tty; + private $pty; + + private $useFileHandles = false; + + /** @var Pipes */ + private $processPipes; + + private $latestSignal; + + private static $sigchild; + + /** + * @var array + */ + public static $exitCodes = [ + 0 => 'OK', + 1 => 'General error', + 2 => 'Misuse of shell builtins', + 126 => 'Invoked command cannot execute', + 127 => 'Command not found', + 128 => 'Invalid exit argument', + // signals + 129 => 'Hangup', + 130 => 'Interrupt', + 131 => 'Quit and dump core', + 132 => 'Illegal instruction', + 133 => 'Trace/breakpoint trap', + 134 => 'Process aborted', + 135 => 'Bus error: "access to undefined portion of memory object"', + 136 => 'Floating point exception: "erroneous arithmetic operation"', + 137 => 'Kill (terminate immediately)', + 138 => 'User-defined 1', + 139 => 'Segmentation violation', + 140 => 'User-defined 2', + 141 => 'Write to pipe with no one reading', + 142 => 'Signal raised by alarm', + 143 => 'Termination (request to terminate)', + // 144 - not defined + 145 => 'Child process terminated, stopped (or continued*)', + 146 => 'Continue if stopped', + 147 => 'Stop executing temporarily', + 148 => 'Terminal stop signal', + 149 => 'Background process attempting to read from tty ("in")', + 150 => 'Background process attempting to write to tty ("out")', + 151 => 'Urgent data available on socket', + 152 => 'CPU time limit exceeded', + 153 => 'File size limit exceeded', + 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"', + 155 => 'Profiling timer expired', + // 156 - not defined + 157 => 'Pollable event', + // 158 - not defined + 159 => 'Bad syscall', + ]; + + /** + * 构造方法 + * @access public + * @param string $commandline 指令 + * @param string|null $cwd 工作目录 + * @param array|null $env 环境变量 + * @param string|null $input 输入 + * @param int|float|null $timeout 超时时间 + * @param array $options proc_open的选项 + * @throws \RuntimeException + * @api + */ + public function __construct($commandline, $cwd = null, array $env = null, $input = null, $timeout = 60, array $options = []) + { + if (!function_exists('proc_open')) { + throw new \RuntimeException('The Process class relies on proc_open, which is not available on your PHP installation.'); + } + + $this->commandline = $commandline; + $this->cwd = $cwd; + + if (null === $this->cwd && (defined('ZEND_THREAD_SAFE') || '\\' === DIRECTORY_SEPARATOR)) { + $this->cwd = getcwd(); + } + if (null !== $env) { + $this->setEnv($env); + } + + $this->input = $input; + $this->setTimeout($timeout); + $this->useFileHandles = '\\' === DIRECTORY_SEPARATOR; + $this->pty = false; + $this->enhanceWindowsCompatibility = true; + $this->enhanceSigchildCompatibility = '\\' !== DIRECTORY_SEPARATOR && $this->isSigchildEnabled(); + $this->options = array_replace([ + 'suppress_errors' => true, + 'binary_pipes' => true, + ], $options); + } + + public function __destruct() + { + $this->stop(); + } + + public function __clone() + { + $this->resetProcessData(); + } + + /** + * 运行指令 + * @access public + * @param callback|null $callback + * @return int + */ + public function run($callback = null) + { + $this->start($callback); + + return $this->wait(); + } + + /** + * 运行指令 + * @access public + * @param callable|null $callback + * @return self + * @throws \RuntimeException + * @throws ProcessFailedException + */ + public function mustRun($callback = null) + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + if (0 !== $this->run($callback)) { + throw new ProcessFailedException($this); + } + + return $this; + } + + /** + * 启动进程并写到 STDIN 输入后返回。 + * @access public + * @param callable|null $callback + * @throws \RuntimeException + * @throws \RuntimeException + * @throws \LogicException + */ + public function start($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + if ($this->outputDisabled && null !== $callback) { + throw new \LogicException('Output has been disabled, enable it to allow the use of a callback.'); + } + + $this->resetProcessData(); + $this->starttime = $this->lastOutputTime = microtime(true); + $this->callback = $this->buildCallback($callback); + $descriptors = $this->getDescriptors(); + + $commandline = $this->commandline; + + if ('\\' === DIRECTORY_SEPARATOR && $this->enhanceWindowsCompatibility) { + $commandline = 'cmd /V:ON /E:ON /C "(' . $commandline . ')'; + foreach ($this->processPipes->getFiles() as $offset => $filename) { + $commandline .= ' ' . $offset . '>' . Utils::escapeArgument($filename); + } + $commandline .= '"'; + + if (!isset($this->options['bypass_shell'])) { + $this->options['bypass_shell'] = true; + } + } + + $this->process = proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $this->env, $this->options); + + if (!is_resource($this->process)) { + throw new \RuntimeException('Unable to launch a new process.'); + } + $this->status = self::STATUS_STARTED; + + if ($this->tty) { + return; + } + + $this->updateStatus(false); + $this->checkTimeout(); + } + + /** + * 重启进程 + * @access public + * @param callable|null $callback + * @return Process + * @throws \RuntimeException + * @throws \RuntimeException + */ + public function restart($callback = null) + { + if ($this->isRunning()) { + throw new \RuntimeException('Process is already running'); + } + + $process = clone $this; + $process->start($callback); + + return $process; + } + + /** + * 等待要终止的进程 + * @access public + * @param callable|null $callback + * @return int + */ + public function wait($callback = null) + { + $this->requireProcessIsStarted(__FUNCTION__); + + $this->updateStatus(false); + if (null !== $callback) { + $this->callback = $this->buildCallback($callback); + } + + do { + $this->checkTimeout(); + $running = '\\' === DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen(); + $close = '\\' !== DIRECTORY_SEPARATOR || !$running; + $this->readPipes(true, $close); + } while ($running); + + while ($this->isRunning()) { + usleep(1000); + } + + if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) { + throw new \RuntimeException(sprintf('The process has been signaled with signal "%s".', $this->processInformation['termsig'])); + } + + return $this->exitcode; + } + + /** + * 获取PID + * @access public + * @return int|null + * @throws \RuntimeException + */ + public function getPid() + { + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process identifier can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->isRunning() ? $this->processInformation['pid'] : null; + } + + /** + * 将一个 POSIX 信号发送到进程中 + * @access public + * @param int $signal + * @return Process + */ + public function signal($signal) + { + $this->doSignal($signal, true); + + return $this; + } + + /** + * 禁用从底层过程获取输出和错误输出。 + * @access public + * @return Process + */ + public function disableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Disabling output while the process is running is not possible.'); + } + if (null !== $this->idleTimeout) { + throw new \LogicException('Output can not be disabled while an idle timeout is set.'); + } + + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启从底层过程获取输出和错误输出。 + * @access public + * @return Process + * @throws \RuntimeException + */ + public function enableOutput() + { + if ($this->isRunning()) { + throw new \RuntimeException('Enabling output while the process is running is not possible.'); + } + + $this->outputDisabled = false; + + return $this; + } + + /** + * 输出是否禁用 + * @access public + * @return bool + */ + public function isOutputDisabled() + { + return $this->outputDisabled; + } + + /** + * 获取当前的输出管道 + * @access public + * @return string + * @throws \LogicException + * @api + */ + public function getOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + return $this->stdout; + } + + /** + * 以增量方式返回的输出结果。 + * @access public + * @return string + */ + public function getIncrementalOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getOutput(); + + $latest = substr($data, $this->incrementalOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空输出 + * @access public + * @return Process + */ + public function clearOutput() + { + $this->stdout = ''; + $this->incrementalOutputOffset = 0; + + return $this; + } + + /** + * 返回当前的错误输出的过程 (STDERR)。 + * @access public + * @return string + */ + public function getErrorOutput() + { + if ($this->outputDisabled) { + throw new \LogicException('Output has been disabled.'); + } + + $this->requireProcessIsStarted(__FUNCTION__); + + $this->readPipes(false, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + return $this->stderr; + } + + /** + * 以增量方式返回 errorOutput + * @access public + * @return string + */ + public function getIncrementalErrorOutput() + { + $this->requireProcessIsStarted(__FUNCTION__); + + $data = $this->getErrorOutput(); + + $latest = substr($data, $this->incrementalErrorOutputOffset); + + if (false === $latest) { + return ''; + } + + $this->incrementalErrorOutputOffset = strlen($data); + + return $latest; + } + + /** + * 清空 errorOutput + * @access public + * @return Process + */ + public function clearErrorOutput() + { + $this->stderr = ''; + $this->incrementalErrorOutputOffset = 0; + + return $this; + } + + /** + * 获取退出码 + * @access public + * @return null|int + */ + public function getExitCode() + { + if ($this->isSigchildEnabled() && !$this->enhanceSigchildCompatibility) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. You must use setEnhanceSigchildCompatibility() to use this method.'); + } + + $this->updateStatus(false); + + return $this->exitcode; + } + + /** + * 获取退出文本 + * @access public + * @return null|string + */ + public function getExitCodeText() + { + if (null === $exitcode = $this->getExitCode()) { + return; + } + + return isset(self::$exitCodes[$exitcode]) ? self::$exitCodes[$exitcode] : 'Unknown error'; + } + + /** + * 检查是否成功 + * @access public + * @return bool + */ + public function isSuccessful() + { + return 0 === $this->getExitCode(); + } + + /** + * 是否未捕获的信号已被终止子进程 + * @access public + * @return bool + */ + public function hasBeenSignaled() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['signaled']; + } + + /** + * 返回导致子进程终止其执行的数。 + * @access public + * @return int + */ + public function getTermSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + if ($this->isSigchildEnabled()) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal can not be retrieved.'); + } + + $this->updateStatus(false); + + return $this->processInformation['termsig']; + } + + /** + * 检查子进程信号是否已停止 + * @access public + * @return bool + */ + public function hasBeenStopped() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopped']; + } + + /** + * 返回导致子进程停止其执行的数。 + * @access public + * @return int + */ + public function getStopSignal() + { + $this->requireProcessIsTerminated(__FUNCTION__); + + $this->updateStatus(false); + + return $this->processInformation['stopsig']; + } + + /** + * 检查是否正在运行 + * @access public + * @return bool + */ + public function isRunning() + { + if (self::STATUS_STARTED !== $this->status) { + return false; + } + + $this->updateStatus(false); + + return $this->processInformation['running']; + } + + /** + * 检查是否已开始 + * @access public + * @return bool + */ + public function isStarted() + { + return self::STATUS_READY != $this->status; + } + + /** + * 检查是否已终止 + * @access public + * @return bool + */ + public function isTerminated() + { + $this->updateStatus(false); + + return self::STATUS_TERMINATED == $this->status; + } + + /** + * 获取当前的状态 + * @access public + * @return string + */ + public function getStatus() + { + $this->updateStatus(false); + + return $this->status; + } + + /** + * 终止进程 + * @access public + */ + public function stop() + { + if ($this->isRunning()) { + if ('\\' === DIRECTORY_SEPARATOR && !$this->isSigchildEnabled()) { + exec(sprintf('taskkill /F /T /PID %d 2>&1', $this->getPid()), $output, $exitCode); + if ($exitCode > 0) { + throw new \RuntimeException('Unable to kill the process'); + } + } else { + $pids = preg_split('/\s+/', `ps -o pid --no-heading --ppid {$this->getPid()}`); + foreach ($pids as $pid) { + if (is_numeric($pid)) { + posix_kill($pid, 9); + } + } + } + } + + $this->updateStatus(false); + if ($this->processInformation['running']) { + $this->close(); + } + + return $this->exitcode; + } + + /** + * 添加一行输出 + * @access public + * @param string $line + */ + public function addOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stdout .= $line; + } + + /** + * 添加一行错误输出 + * @access public + * @param string $line + */ + public function addErrorOutput($line) +{ + $this->lastOutputTime = microtime(true); + $this->stderr .= $line; + } + + /** + * 获取被执行的指令 + * @access public + * @return string + */ + public function getCommandLine() +{ + return $this->commandline; + } + + /** + * 设置指令 + * @access public + * @param string $commandline + * @return self + */ + public function setCommandLine($commandline) +{ + $this->commandline = $commandline; + + return $this; + } + + /** + * 获取超时时间 + * @access public + * @return float|null + */ + public function getTimeout() +{ + return $this->timeout; + } + + /** + * 获取idle超时时间 + * @access public + * @return float|null + */ + public function getIdleTimeout() +{ + return $this->idleTimeout; + } + + /** + * 设置超时时间 + * @access public + * @param int|float|null $timeout + * @return self + */ + public function setTimeout($timeout) +{ + $this->timeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置idle超时时间 + * @access public + * @param int|float|null $timeout + * @return self + */ + public function setIdleTimeout($timeout) +{ + if (null !== $timeout && $this->outputDisabled) { + throw new \LogicException('Idle timeout can not be set while the output is disabled.'); + } + + $this->idleTimeout = $this->validateTimeout($timeout); + + return $this; + } + + /** + * 设置TTY + * @access public + * @param bool $tty + * @return self + */ + public function setTty($tty) +{ + if ('\\' === DIRECTORY_SEPARATOR && $tty) { + throw new \RuntimeException('TTY mode is not supported on Windows platform.'); + } + if ($tty && (!file_exists('/dev/tty') || !is_readable('/dev/tty'))) { + throw new \RuntimeException('TTY mode requires /dev/tty to be readable.'); + } + + $this->tty = (bool) $tty; + + return $this; + } + + /** + * 检查是否是tty模式 + * @access public + * @return bool + */ + public function isTty() +{ + return $this->tty; + } + + /** + * 设置pty模式 + * @access public + * @param bool $bool + * @return self + */ + public function setPty($bool) +{ + $this->pty = (bool) $bool; + + return $this; + } + + /** + * 是否是pty模式 + * @access public + * @return bool + */ + public function isPty() +{ + return $this->pty; + } + + /** + * 获取工作目录 + * @access public + * @return string|null + */ + public function getWorkingDirectory() +{ + if (null === $this->cwd) { + return getcwd() ?: null; + } + + return $this->cwd; + } + + /** + * 设置工作目录 + * @access public + * @param string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) +{ + $this->cwd = $cwd; + + return $this; + } + + /** + * 获取环境变量 + * @access public + * @return array + */ + public function getEnv() +{ + return $this->env; + } + + /** + * 设置环境变量 + * @access public + * @param array $env + * @return self + */ + public function setEnv(array $env) +{ + $env = array_filter($env, function ($value) { + return !is_array($value); + }); + + $this->env = []; + foreach ($env as $key => $value) { + $this->env[(binary) $key] = (binary) $value; + } + + return $this; + } + + /** + * 获取输入 + * @access public + * @return null|string + */ + public function getInput() +{ + return $this->input; + } + + /** + * 设置输入 + * @access public + * @param mixed $input + * @return self + */ + public function setInput($input) +{ + if ($this->isRunning()) { + throw new \LogicException('Input can not be set while the process is running.'); + } + + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 获取proc_open的选项 + * @access public + * @return array + */ + public function getOptions() +{ + return $this->options; + } + + /** + * 设置proc_open的选项 + * @access public + * @param array $options + * @return self + */ + public function setOptions(array $options) +{ + $this->options = $options; + + return $this; + } + + /** + * 是否兼容windows + * @access public + * @return bool + */ + public function getEnhanceWindowsCompatibility() +{ + return $this->enhanceWindowsCompatibility; + } + + /** + * 设置是否兼容windows + * @access public + * @param bool $enhance + * @return self + */ + public function setEnhanceWindowsCompatibility($enhance) +{ + $this->enhanceWindowsCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 返回是否 sigchild 兼容模式激活 + * @access public + * @return bool + */ + public function getEnhanceSigchildCompatibility() +{ + return $this->enhanceSigchildCompatibility; + } + + /** + * 激活 sigchild 兼容性模式。 + * @access public + * @param bool $enhance + * @return self + */ + public function setEnhanceSigchildCompatibility($enhance) +{ + $this->enhanceSigchildCompatibility = (bool) $enhance; + + return $this; + } + + /** + * 是否超时 + */ + public function checkTimeout() +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_GENERAL); + } + + if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) { + $this->stop(); + + throw new ProcessTimeoutException($this, ProcessTimeoutException::TYPE_IDLE); + } + } + + /** + * 是否支持pty + * @access public + * @return bool + */ + public static function isPtySupported() +{ + static $result; + + if (null !== $result) { + return $result; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + return $result = false; + } + + $proc = @proc_open('echo 1', [['pty'], ['pty'], ['pty']], $pipes); + if (is_resource($proc)) { + proc_close($proc); + + return $result = true; + } + + return $result = false; + } + + /** + * 创建所需的 proc_open 的描述符 + * @access private + * @return array + */ + private function getDescriptors() +{ + if ('\\' === DIRECTORY_SEPARATOR) { + $this->processPipes = WindowsPipes::create($this, $this->input); + } else { + $this->processPipes = UnixPipes::create($this, $this->input); + } + $descriptors = $this->processPipes->getDescriptors($this->outputDisabled); + + if (!$this->useFileHandles && $this->enhanceSigchildCompatibility && $this->isSigchildEnabled()) { + + $descriptors = array_merge($descriptors, [['pipe', 'w']]); + + $this->commandline = '(' . $this->commandline . ') 3>/dev/null; code=$?; echo $code >&3; exit $code'; + } + + return $descriptors; + } + + /** + * 建立 wait () 使用的回调。 + * @access protected + * @param callable|null $callback + * @return callable + */ + protected function buildCallback($callback) +{ + $out = self::OUT; + $callback = function ($type, $data) use ($callback, $out) { + if ($out == $type) { + $this->addOutput($data); + } else { + $this->addErrorOutput($data); + } + + if (null !== $callback) { + call_user_func($callback, $type, $data); + } + }; + + return $callback; + } + + /** + * 更新状态 + * @access protected + * @param bool $blocking + */ + protected function updateStatus($blocking) +{ + if (self::STATUS_STARTED !== $this->status) { + return; + } + + $this->processInformation = proc_get_status($this->process); + $this->captureExitCode(); + + $this->readPipes($blocking, '\\' === DIRECTORY_SEPARATOR ? !$this->processInformation['running'] : true); + + if (!$this->processInformation['running']) { + $this->close(); + } + } + + /** + * 是否开启 '--enable-sigchild' + * @access protected + * @return bool + */ + protected function isSigchildEnabled() +{ + if (null !== self::$sigchild) { + return self::$sigchild; + } + + if (!function_exists('phpinfo')) { + return self::$sigchild = false; + } + + ob_start(); + phpinfo(INFO_GENERAL); + + return self::$sigchild = false !== strpos(ob_get_clean(), '--enable-sigchild'); + } + + /** + * 验证是否超时 + * @access private + * @param int|float|null $timeout + * @return float|null + */ + private function validateTimeout($timeout) +{ + $timeout = (float) $timeout; + + if (0.0 === $timeout) { + $timeout = null; + } elseif ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + return $timeout; + } + + /** + * 读取pipes + * @access private + * @param bool $blocking + * @param bool $close + */ + private function readPipes($blocking, $close) +{ + $result = $this->processPipes->readAndWrite($blocking, $close); + + $callback = $this->callback; + foreach ($result as $type => $data) { + if (3 == $type) { + $this->fallbackExitcode = (int) $data; + } else { + $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data); + } + } + } + + /** + * 捕获退出码 + */ + private function captureExitCode() +{ + if (isset($this->processInformation['exitcode']) && -1 != $this->processInformation['exitcode']) { + $this->exitcode = $this->processInformation['exitcode']; + } + } + + /** + * 关闭资源 + * @access private + * @return int 退出码 + */ + private function close() +{ + $this->processPipes->close(); + if (is_resource($this->process)) { + $exitcode = proc_close($this->process); + } else { + $exitcode = -1; + } + + $this->exitcode = -1 !== $exitcode ? $exitcode : (null !== $this->exitcode ? $this->exitcode : -1); + $this->status = self::STATUS_TERMINATED; + + if (-1 === $this->exitcode && null !== $this->fallbackExitcode) { + $this->exitcode = $this->fallbackExitcode; + } elseif (-1 === $this->exitcode && $this->processInformation['signaled'] + && 0 < $this->processInformation['termsig'] + ) { + $this->exitcode = 128 + $this->processInformation['termsig']; + } + + return $this->exitcode; + } + + /** + * 重置数据 + */ + private function resetProcessData() +{ + $this->starttime = null; + $this->callback = null; + $this->exitcode = null; + $this->fallbackExitcode = null; + $this->processInformation = null; + $this->stdout = null; + $this->stderr = null; + $this->process = null; + $this->latestSignal = null; + $this->status = self::STATUS_READY; + $this->incrementalOutputOffset = 0; + $this->incrementalErrorOutputOffset = 0; + } + + /** + * 将一个 POSIX 信号发送到进程中。 + * @access private + * @param int $signal + * @param bool $throwException + * @return bool + */ + private function doSignal($signal, $throwException) +{ + if (!$this->isRunning()) { + if ($throwException) { + throw new \LogicException('Can not send signal on a non running process.'); + } + + return false; + } + + if ($this->isSigchildEnabled()) { + if ($throwException) { + throw new \RuntimeException('This PHP has been compiled with --enable-sigchild. The process can not be signaled.'); + } + + return false; + } + + if (true !== @proc_terminate($this->process, $signal)) { + if ($throwException) { + throw new \RuntimeException(sprintf('Error while sending signal `%s`.', $signal)); + } + + return false; + } + + $this->latestSignal = $signal; + + return true; + } + + /** + * 确保进程已经开启 + * @access private + * @param string $functionName + */ + private function requireProcessIsStarted($functionName) +{ + if (!$this->isStarted()) { + throw new \LogicException(sprintf('Process must be started before calling %s.', $functionName)); + } + } + + /** + * 确保进程已经终止 + * @access private + * @param string $functionName + */ + private function requireProcessIsTerminated($functionName) +{ + if (!$this->isTerminated()) { + throw new \LogicException(sprintf('Process must be terminated before calling %s.', $functionName)); + } + } +} diff --git a/thinkphp/library/think/Request.php b/thinkphp/library/think/Request.php new file mode 100644 index 0000000..6ba6e7d --- /dev/null +++ b/thinkphp/library/think/Request.php @@ -0,0 +1,2246 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\facade\Cookie; +use think\facade\Session; + +class Request +{ + /** + * 配置参数 + * @var array + */ + protected $config = [ + // 表单请求类型伪装变量 + 'var_method' => '_method', + // 表单ajax伪装变量 + 'var_ajax' => '_ajax', + // 表单pjax伪装变量 + 'var_pjax' => '_pjax', + // PATHINFO变量名 用于兼容模式 + 'var_pathinfo' => 's', + // 兼容PATH_INFO获取 + 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], + // 默认全局过滤方法 用逗号分隔多个 + 'default_filter' => '', + // 域名根,如thinkphp.cn + 'url_domain_root' => '', + // HTTPS代理标识 + 'https_agent_name' => '', + // IP代理获取标识 + 'http_agent_ip' => 'HTTP_X_REAL_IP', + // URL伪静态后缀 + 'url_html_suffix' => 'html', + ]; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 主机名(含端口) + * @var string + */ + protected $host; + + /** + * 域名(含协议及端口) + * @var string + */ + protected $domain; + + /** + * 子域名 + * @var string + */ + protected $subDomain; + + /** + * 泛域名 + * @var string + */ + protected $panDomain; + + /** + * 当前URL地址 + * @var string + */ + protected $url; + + /** + * 基础URL + * @var string + */ + protected $baseUrl; + + /** + * 当前执行的文件 + * @var string + */ + protected $baseFile; + + /** + * 访问的ROOT地址 + * @var string + */ + protected $root; + + /** + * pathinfo + * @var string + */ + protected $pathinfo; + + /** + * pathinfo(不含后缀) + * @var string + */ + protected $path; + + /** + * 当前路由信息 + * @var array + */ + protected $routeInfo = []; + + /** + * 当前调度信息 + * @var \think\route\Dispatch + */ + protected $dispatch; + + /** + * 当前模块名 + * @var string + */ + protected $module; + + /** + * 当前控制器名 + * @var string + */ + protected $controller; + + /** + * 当前操作名 + * @var string + */ + protected $action; + + /** + * 当前语言集 + * @var string + */ + protected $langset; + + /** + * 当前请求参数 + * @var array + */ + protected $param = []; + + /** + * 当前GET参数 + * @var array + */ + protected $get = []; + + /** + * 当前POST参数 + * @var array + */ + protected $post = []; + + /** + * 当前REQUEST参数 + * @var array + */ + protected $request = []; + + /** + * 当前ROUTE参数 + * @var array + */ + protected $route = []; + + /** + * 当前PUT参数 + * @var array + */ + protected $put; + + /** + * 当前SESSION参数 + * @var array + */ + protected $session = []; + + /** + * 当前FILE参数 + * @var array + */ + protected $file = []; + + /** + * 当前COOKIE参数 + * @var array + */ + protected $cookie = []; + + /** + * 当前SERVER参数 + * @var array + */ + protected $server = []; + + /** + * 当前ENV参数 + * @var array + */ + protected $env = []; + + /** + * 当前HEADER参数 + * @var array + */ + protected $header = []; + + /** + * 资源类型定义 + * @var array + */ + protected $mimeType = [ + 'xml' => 'application/xml,text/xml,application/x-xml', + 'json' => 'application/json,text/x-json,application/jsonrequest,text/json', + 'js' => 'text/javascript,application/javascript,application/x-javascript', + 'css' => 'text/css', + 'rss' => 'application/rss+xml', + 'yaml' => 'application/x-yaml,text/yaml', + 'atom' => 'application/atom+xml', + 'pdf' => 'application/pdf', + 'text' => 'text/plain', + 'image' => 'image/png,image/jpg,image/jpeg,image/pjpeg,image/gif,image/webp,image/*', + 'csv' => 'text/csv', + 'html' => 'text/html,application/xhtml+xml,*/*', + ]; + + /** + * 当前请求内容 + * @var string + */ + protected $content; + + /** + * 全局过滤规则 + * @var array + */ + protected $filter; + + /** + * 扩展方法 + * @var array + */ + protected $hook = []; + + /** + * php://input内容 + * @var string + */ + protected $input; + + /** + * 请求缓存 + * @var array + */ + protected $cache; + + /** + * 缓存是否检查 + * @var bool + */ + protected $isCheckCache; + + /** + * 请求安全Key + * @var string + */ + protected $secureKey; + + /** + * 是否合并Param + * @var bool + */ + protected $mergeParam = false; + + /** + * 架构函数 + * @access public + * @param array $options 参数 + */ + public function __construct(array $options = []) + { + $this->init($options); + + // 保存 php://input + $this->input = file_get_contents('php://input'); + } + + public function init(array $options = []) + { + $this->config = array_merge($this->config, $options); + + if (is_null($this->filter) && !empty($this->config['default_filter'])) { + $this->filter = $this->config['default_filter']; + } + } + + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + public static function __make(App $app, Config $config) + { + $request = new static($config->pull('app')); + + $request->server = $_SERVER; + $request->env = $app['env']->get(); + + return $request; + } + + public function __call($method, $args) + { + if (array_key_exists($method, $this->hook)) { + array_unshift($args, $this); + return call_user_func_array($this->hook[$method], $args); + } + + throw new Exception('method not exists:' . static::class . '->' . $method); + } + + /** + * Hook 方法注入 + * @access public + * @param string|array $method 方法名 + * @param mixed $callback callable + * @return void + */ + public function hook($method, $callback = null) + { + if (is_array($method)) { + $this->hook = array_merge($this->hook, $method); + } else { + $this->hook[$method] = $callback; + } + } + + /** + * 创建一个URL请求 + * @access public + * @param string $uri URL地址 + * @param string $method 请求类型 + * @param array $params 请求参数 + * @param array $cookie + * @param array $files + * @param array $server + * @param string $content + * @return \think\Request + */ + public function create($uri, $method = 'GET', $params = [], $cookie = [], $files = [], $server = [], $content = null) + { + $server['PATH_INFO'] = ''; + $server['REQUEST_METHOD'] = strtoupper($method); + $info = parse_url($uri); + + if (isset($info['host'])) { + $server['SERVER_NAME'] = $info['host']; + $server['HTTP_HOST'] = $info['host']; + } + + if (isset($info['scheme'])) { + if ('https' === $info['scheme']) { + $server['HTTPS'] = 'on'; + $server['SERVER_PORT'] = 443; + } else { + unset($server['HTTPS']); + $server['SERVER_PORT'] = 80; + } + } + + if (isset($info['port'])) { + $server['SERVER_PORT'] = $info['port']; + $server['HTTP_HOST'] = $server['HTTP_HOST'] . ':' . $info['port']; + } + + if (isset($info['user'])) { + $server['PHP_AUTH_USER'] = $info['user']; + } + + if (isset($info['pass'])) { + $server['PHP_AUTH_PW'] = $info['pass']; + } + + if (!isset($info['path'])) { + $info['path'] = '/'; + } + + $options = []; + $queryString = ''; + + $options[strtolower($method)] = $params; + + if (isset($info['query'])) { + parse_str(html_entity_decode($info['query']), $query); + if (!empty($params)) { + $params = array_replace($query, $params); + $queryString = http_build_query($params, '', '&'); + } else { + $params = $query; + $queryString = $info['query']; + } + } elseif (!empty($params)) { + $queryString = http_build_query($params, '', '&'); + } + + if ($queryString) { + parse_str($queryString, $get); + $options['get'] = isset($options['get']) ? array_merge($get, $options['get']) : $get; + } + + $server['REQUEST_URI'] = $info['path'] . ('' !== $queryString ? '?' . $queryString : ''); + $server['QUERY_STRING'] = $queryString; + $options['cookie'] = $cookie; + $options['param'] = $params; + $options['file'] = $files; + $options['server'] = $server; + $options['url'] = $server['REQUEST_URI']; + $options['baseUrl'] = $info['path']; + $options['pathinfo'] = '/' == $info['path'] ? '/' : ltrim($info['path'], '/'); + $options['method'] = $server['REQUEST_METHOD']; + $options['domain'] = isset($info['scheme']) ? $info['scheme'] . '://' . $server['HTTP_HOST'] : ''; + $options['content'] = $content; + + $request = new static(); + foreach ($options as $name => $item) { + if (property_exists($request, $name)) { + $request->$name = $item; + } + } + + return $request; + } + + /** + * 获取当前包含协议、端口的域名 + * @access public + * @param bool $port 是否需要去除端口号 + * @return string + */ + public function domain($port = false) + { + return $this->scheme() . '://' . $this->host($port); + } + + /** + * 获取当前根域名 + * @access public + * @return string + */ + public function rootDomain() + { + $root = $this->config['url_domain_root']; + + if (!$root) { + $item = explode('.', $this->host(true)); + $count = count($item); + $root = $count > 1 ? $item[$count - 2] . '.' . $item[$count - 1] : $item[0]; + } + + return $root; + } + + /** + * 获取当前子域名 + * @access public + * @return string + */ + public function subDomain() + { + if (is_null($this->subDomain)) { + // 获取当前主域名 + $rootDomain = $this->config['url_domain_root']; + + if ($rootDomain) { + // 配置域名根 例如 thinkphp.cn 163.com.cn 如果是国家级域名 com.cn net.cn 之类的域名需要配置 + $domain = explode('.', rtrim(stristr($this->host(true), $rootDomain, true), '.')); + } else { + $domain = explode('.', $this->host(true), -2); + } + + $this->subDomain = implode('.', $domain); + } + + return $this->subDomain; + } + + /** + * 设置当前泛域名的值 + * @access public + * @param string $domain 域名 + * @return $this + */ + public function setPanDomain($domain) + { + $this->panDomain = $domain; + return $this; + } + + /** + * 获取当前泛域名的值 + * @access public + * @return string + */ + public function panDomain() + { + return $this->panDomain; + } + + /** + * 设置当前完整URL 包括QUERY_STRING + * @access public + * @param string $url URL + * @return $this + */ + public function setUrl($url) + { + $this->url = $url; + return $this; + } + + /** + * 获取当前完整URL 包括QUERY_STRING + * @access public + * @param bool $complete 是否包含域名 + * @return string + */ + public function url($complete = false) + { + if (!$this->url) { + if ($this->isCli()) { + $this->url = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ($this->server('HTTP_X_REWRITE_URL')) { + $this->url = $this->server('HTTP_X_REWRITE_URL'); + } elseif ($this->server('REQUEST_URI')) { + $this->url = $this->server('REQUEST_URI'); + } elseif ($this->server('ORIG_PATH_INFO')) { + $this->url = $this->server('ORIG_PATH_INFO') . (!empty($this->server('QUERY_STRING')) ? '?' . $this->server('QUERY_STRING') : ''); + } else { + $this->url = ''; + } + } + + return $complete ? $this->domain() . $this->url : $this->url; + } + + /** + * 设置当前完整URL 不包括QUERY_STRING + * @access public + * @param string $url URL + * @return $this + */ + public function setBaseUrl($url) + { + $this->baseUrl = $url; + return $this; + } + + /** + * 获取当前URL 不含QUERY_STRING + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function baseUrl($domain = false) + { + if (!$this->baseUrl) { + $str = $this->url(); + $this->baseUrl = strpos($str, '?') ? strstr($str, '?', true) : $str; + } + + return $domain ? $this->domain() . $this->baseUrl : $this->baseUrl; + } + + /** + * 设置或获取当前执行的文件 SCRIPT_NAME + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function baseFile($domain = false) + { + if (!$this->baseFile) { + $url = ''; + if (!$this->isCli()) { + $script_name = basename($this->server('SCRIPT_FILENAME')); + if (basename($this->server('SCRIPT_NAME')) === $script_name) { + $url = $this->server('SCRIPT_NAME'); + } elseif (basename($this->server('PHP_SELF')) === $script_name) { + $url = $this->server('PHP_SELF'); + } elseif (basename($this->server('ORIG_SCRIPT_NAME')) === $script_name) { + $url = $this->server('ORIG_SCRIPT_NAME'); + } elseif (($pos = strpos($this->server('PHP_SELF'), '/' . $script_name)) !== false) { + $url = substr($this->server('SCRIPT_NAME'), 0, $pos) . '/' . $script_name; + } elseif ($this->server('DOCUMENT_ROOT') && strpos($this->server('SCRIPT_FILENAME'), $this->server('DOCUMENT_ROOT')) === 0) { + $url = str_replace('\\', '/', str_replace($this->server('DOCUMENT_ROOT'), '', $this->server('SCRIPT_FILENAME'))); + } + } + $this->baseFile = $url; + } + + return $domain ? $this->domain() . $this->baseFile : $this->baseFile; + } + + /** + * 设置URL访问根地址 + * @access public + * @param string $url URL地址 + * @return string|$this + */ + public function setRoot($url = null) + { + $this->root = $url; + return $this; + } + + /** + * 获取URL访问根地址 + * @access public + * @param bool $domain 是否包含域名 + * @return string|$this + */ + public function root($domain = false) + { + if (!$this->root) { + $file = $this->baseFile(); + if ($file && 0 !== strpos($this->url(), $file)) { + $file = str_replace('\\', '/', dirname($file)); + } + $this->root = rtrim($file, '/'); + } + + return $domain ? $this->domain() . $this->root : $this->root; + } + + /** + * 获取URL访问根目录 + * @access public + * @return string + */ + public function rootUrl() + { + $base = $this->root(); + $root = strpos($base, '.') ? ltrim(dirname($base), DIRECTORY_SEPARATOR) : $base; + + if ('' != $root) { + $root = '/' . ltrim($root, '/'); + } + + return $root; + } + + public function setPathinfo($pathinfo) + { + $this->pathinfo = $pathinfo; + return $this; + } + + /** + * 获取当前请求URL的pathinfo信息(含URL后缀) + * @access public + * @return string + */ + public function pathinfo() + { + if (is_null($this->pathinfo)) { + if (isset($_GET[$this->config['var_pathinfo']])) { + // 判断URL里面是否有兼容模式参数 + $pathinfo = $_GET[$this->config['var_pathinfo']]; + unset($_GET[$this->config['var_pathinfo']]); + } elseif ($this->isCli()) { + // CLI模式下 index.php module/controller/action/params/... + $pathinfo = isset($_SERVER['argv'][1]) ? $_SERVER['argv'][1] : ''; + } elseif ('cli-server' == PHP_SAPI) { + $pathinfo = strpos($this->server('REQUEST_URI'), '?') ? strstr($this->server('REQUEST_URI'), '?', true) : $this->server('REQUEST_URI'); + } elseif ($this->server('PATH_INFO')) { + $pathinfo = $this->server('PATH_INFO'); + } + + // 分析PATHINFO信息 + if (!isset($pathinfo)) { + foreach ($this->config['pathinfo_fetch'] as $type) { + if ($this->server($type)) { + $pathinfo = (0 === strpos($this->server($type), $this->server('SCRIPT_NAME'))) ? + substr($this->server($type), strlen($this->server('SCRIPT_NAME'))) : $this->server($type); + break; + } + } + } + + $this->pathinfo = empty($pathinfo) || '/' == $pathinfo ? '' : ltrim($pathinfo, '/'); + } + + return $this->pathinfo; + } + + /** + * 获取当前请求URL的pathinfo信息(不含URL后缀) + * @access public + * @return string + */ + public function path() + { + if (is_null($this->path)) { + $suffix = $this->config['url_html_suffix']; + $pathinfo = $this->pathinfo(); + + if (false === $suffix) { + // 禁止伪静态访问 + $this->path = $pathinfo; + } elseif ($suffix) { + // 去除正常的URL后缀 + $this->path = preg_replace('/\.(' . ltrim($suffix, '.') . ')$/i', '', $pathinfo); + } else { + // 允许任何后缀访问 + $this->path = preg_replace('/\.' . $this->ext() . '$/i', '', $pathinfo); + } + } + + return $this->path; + } + + /** + * 当前URL的访问后缀 + * @access public + * @return string + */ + public function ext() + { + return pathinfo($this->pathinfo(), PATHINFO_EXTENSION); + } + + /** + * 获取当前请求的时间 + * @access public + * @param bool $float 是否使用浮点类型 + * @return integer|float + */ + public function time($float = false) + { + return $float ? $this->server('REQUEST_TIME_FLOAT') : $this->server('REQUEST_TIME'); + } + + /** + * 当前请求的资源类型 + * @access public + * @return false|string + */ + public function type() + { + $accept = $this->server('HTTP_ACCEPT'); + + if (empty($accept)) { + return false; + } + + foreach ($this->mimeType as $key => $val) { + $array = explode(',', $val); + foreach ($array as $k => $v) { + if (stristr($accept, $v)) { + return $key; + } + } + } + + return false; + } + + /** + * 设置资源类型 + * @access public + * @param string|array $type 资源类型名 + * @param string $val 资源类型 + * @return void + */ + public function mimeType($type, $val = '') + { + if (is_array($type)) { + $this->mimeType = array_merge($this->mimeType, $type); + } else { + $this->mimeType[$type] = $val; + } + } + + /** + * 当前的请求类型 + * @access public + * @param bool $origin 是否获取原始请求类型 + * @return string + */ + public function method($origin = false) + { + if ($origin) { + // 获取原始请求类型 + return $this->server('REQUEST_METHOD') ?: 'GET'; + } elseif (!$this->method) { + if (isset($_POST[$this->config['var_method']])) { + $this->method = strtoupper($_POST[$this->config['var_method']]); + $method = strtolower($this->method); + $this->{$method} = $_POST; + } elseif ($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')) { + $this->method = strtoupper($this->server('HTTP_X_HTTP_METHOD_OVERRIDE')); + } else { + $this->method = $this->server('REQUEST_METHOD') ?: 'GET'; + } + } + + return $this->method; + } + + /** + * 是否为GET请求 + * @access public + * @return bool + */ + public function isGet() + { + return $this->method() == 'GET'; + } + + /** + * 是否为POST请求 + * @access public + * @return bool + */ + public function isPost() + { + return $this->method() == 'POST'; + } + + /** + * 是否为PUT请求 + * @access public + * @return bool + */ + public function isPut() + { + return $this->method() == 'PUT'; + } + + /** + * 是否为DELTE请求 + * @access public + * @return bool + */ + public function isDelete() + { + return $this->method() == 'DELETE'; + } + + /** + * 是否为HEAD请求 + * @access public + * @return bool + */ + public function isHead() + { + return $this->method() == 'HEAD'; + } + + /** + * 是否为PATCH请求 + * @access public + * @return bool + */ + public function isPatch() + { + return $this->method() == 'PATCH'; + } + + /** + * 是否为OPTIONS请求 + * @access public + * @return bool + */ + public function isOptions() + { + return $this->method() == 'OPTIONS'; + } + + /** + * 是否为cli + * @access public + * @return bool + */ + public function isCli() + { + return PHP_SAPI == 'cli'; + } + + /** + * 是否为cgi + * @access public + * @return bool + */ + public function isCgi() + { + return strpos(PHP_SAPI, 'cgi') === 0; + } + + /** + * 获取当前请求的参数 + * @access public + * @param mixed $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function param($name = '', $default = null, $filter = '') + { + if (!$this->mergeParam) { + $method = $this->method(true); + + // 自动获取请求变量 + switch ($method) { + case 'POST': + $vars = $this->post(false); + break; + case 'PUT': + case 'DELETE': + case 'PATCH': + $vars = $this->put(false); + break; + default: + $vars = []; + } + + // 当前请求参数和URL地址中的参数合并 + $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false)); + + $this->mergeParam = true; + } + + if (true === $name) { + // 获取包含文件上传信息的数组 + $file = $this->file(); + $data = is_array($file) ? array_merge($this->param, $file) : $this->param; + + return $this->input($data, '', $default, $filter); + } + + return $this->input($this->param, $name, $default, $filter); + } + + /** + * 设置路由变量 + * @access public + * @param array $route 路由变量 + * @return $this + */ + public function setRouteVars(array $route) + { + $this->route = array_merge($this->route, $route); + return $this; + } + + /** + * 获取路由参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function route($name = '', $default = null, $filter = '') + { + return $this->input($this->route, $name, $default, $filter); + } + + /** + * 获取GET参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function get($name = '', $default = null, $filter = '') + { + if (empty($this->get)) { + $this->get = $_GET; + } + + return $this->input($this->get, $name, $default, $filter); + } + + /** + * 获取POST参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function post($name = '', $default = null, $filter = '') + { + if (empty($this->post)) { + $this->post = !empty($_POST) ? $_POST : $this->getInputData($this->input); + } + + return $this->input($this->post, $name, $default, $filter); + } + + /** + * 获取PUT参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function put($name = '', $default = null, $filter = '') + { + if (is_null($this->put)) { + $this->put = $this->getInputData($this->input); + } + + return $this->input($this->put, $name, $default, $filter); + } + + protected function getInputData($content) + { + if (false !== strpos($this->contentType(), 'application/json') || 0 === strpos($content, '{"')) { + return (array) json_decode($content, true); + } elseif (strpos($content, '=')) { + parse_str($content, $data); + return $data; + } + + return []; + } + + /** + * 获取DELETE参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function delete($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取PATCH参数 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function patch($name = '', $default = null, $filter = '') + { + return $this->put($name, $default, $filter); + } + + /** + * 获取request变量 + * @access public + * @param string|false $name 变量名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function request($name = '', $default = null, $filter = '') + { + if (empty($this->request)) { + $this->request = $_REQUEST; + } + + return $this->input($this->request, $name, $default, $filter); + } + + /** + * 获取session数据 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function session($name = '', $default = null) + { + if (empty($this->session)) { + $this->session = Session::get(); + } + + if ('' === $name) { + return $this->session; + } + + $data = $this->getData($this->session, $name); + + return is_null($data) ? $default : $data; + } + + /** + * 获取cookie参数 + * @access public + * @param string $name 变量名 + * @param string $default 默认值 + * @param string|array $filter 过滤方法 + * @return mixed + */ + public function cookie($name = '', $default = null, $filter = '') + { + if (empty($this->cookie)) { + $this->cookie = Cookie::get(); + } + + if (!empty($name)) { + $data = Cookie::has($name) ? Cookie::get($name) : $default; + } else { + $data = $this->cookie; + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + reset($data); + } else { + $this->filterValue($data, $name, $filter); + } + + return $data; + } + + /** + * 获取server参数 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function server($name = '', $default = null) + { + if (empty($name)) { + return $this->server; + } else { + $name = strtoupper($name); + } + + return isset($this->server[$name]) ? $this->server[$name] : $default; + } + + /** + * 获取上传的文件信息 + * @access public + * @param string $name 名称 + * @return null|array|\think\File + */ + public function file($name = '') + { + if (empty($this->file)) { + $this->file = isset($_FILES) ? $_FILES : []; + } + + $files = $this->file; + if (!empty($files)) { + if (strpos($name, '.')) { + list($name, $sub) = explode('.', $name); + } + + // 处理上传文件 + $array = $this->dealUploadFile($files, $name); + + if ('' === $name) { + // 获取全部文件 + return $array; + } elseif (isset($sub) && isset($array[$name][$sub])) { + return $array[$name][$sub]; + } elseif (isset($array[$name])) { + return $array[$name]; + } + } + + return; + } + + protected function dealUploadFile($files, $name) + { + $array = []; + foreach ($files as $key => $file) { + if ($file instanceof File) { + $array[$key] = $file; + } elseif (is_array($file['name'])) { + $item = []; + $keys = array_keys($file); + $count = count($file['name']); + + for ($i = 0; $i < $count; $i++) { + if ($file['error'][$i] > 0) { + if ($name == $key) { + $this->throwUploadFileError($file['error'][$i]); + } else { + continue; + } + } + + $temp['key'] = $key; + + foreach ($keys as $_key) { + $temp[$_key] = $file[$_key][$i]; + } + + $item[] = (new File($temp['tmp_name']))->setUploadInfo($temp); + } + + $array[$key] = $item; + } else { + if ($file['error'] > 0) { + if ($key == $name) { + $this->throwUploadFileError($file['error']); + } else { + continue; + } + } + + $array[$key] = (new File($file['tmp_name']))->setUploadInfo($file); + } + } + + return $array; + } + + protected function throwUploadFileError($error) + { + static $fileUploadErrors = [ + 1 => 'upload File size exceeds the maximum value', + 2 => 'upload File size exceeds the maximum value', + 3 => 'only the portion of file is uploaded', + 4 => 'no file to uploaded', + 6 => 'upload temp dir not found', + 7 => 'file write error', + ]; + + $msg = $fileUploadErrors[$error]; + + throw new Exception($msg); + } + + /** + * 获取环境变量 + * @access public + * @param string $name 数据名称 + * @param string $default 默认值 + * @return mixed + */ + public function env($name = '', $default = null) + { + if (empty($name)) { + return $this->env; + } else { + $name = strtoupper($name); + } + + return isset($this->env[$name]) ? $this->env[$name] : $default; + } + + /** + * 获取当前的Header + * @access public + * @param string $name header名称 + * @param string $default 默认值 + * @return string|array + */ + public function header($name = '', $default = null) + { + if (empty($this->header)) { + $header = []; + if (function_exists('apache_request_headers') && $result = apache_request_headers()) { + $header = $result; + } else { + $server = $this->server; + foreach ($server as $key => $val) { + if (0 === strpos($key, 'HTTP_')) { + $key = str_replace('_', '-', strtolower(substr($key, 5))); + $header[$key] = $val; + } + } + if (isset($server['CONTENT_TYPE'])) { + $header['content-type'] = $server['CONTENT_TYPE']; + } + if (isset($server['CONTENT_LENGTH'])) { + $header['content-length'] = $server['CONTENT_LENGTH']; + } + } + $this->header = array_change_key_case($header); + } + + if ('' === $name) { + return $this->header; + } + + $name = str_replace('_', '-', strtolower($name)); + + return isset($this->header[$name]) ? $this->header[$name] : $default; + } + + /** + * 递归重置数组指针 + * @access public + * @param array $data 数据源 + * @return void + */ + public function arrayReset(array &$data) { + foreach ($data as &$value) { + if (is_array($value)) { + $this->arrayReset($value); + } + } + reset($data); + } + + /** + * 获取变量 支持过滤和默认值 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @param mixed $default 默认值 + * @param string|array $filter 过滤函数 + * @return mixed + */ + public function input($data = [], $name = '', $default = null, $filter = '') + { + if (false === $name) { + // 获取原始数据 + return $data; + } + + $name = (string) $name; + if ('' != $name) { + // 解析name + if (strpos($name, '/')) { + list($name, $type) = explode('/', $name); + } + + $data = $this->getData($data, $name); + + if (is_null($data)) { + return $default; + } + + if (is_object($data)) { + return $data; + } + } + + // 解析过滤器 + $filter = $this->getFilter($filter, $default); + + if (is_array($data)) { + array_walk_recursive($data, [$this, 'filterValue'], $filter); + if (version_compare(PHP_VERSION, '7.1.0', '<')) { + // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针 + $this->arrayReset($data); + } + } else { + $this->filterValue($data, $name, $filter); + } + + if (isset($type) && $data !== $default) { + // 强制类型转换 + $this->typeCast($data, $type); + } + + return $data; + } + + /** + * 获取数据 + * @access public + * @param array $data 数据源 + * @param string|false $name 字段名 + * @return mixed + */ + protected function getData(array $data, $name) + { + foreach (explode('.', $name) as $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + return; + } + } + + return $data; + } + + /** + * 设置或获取当前的过滤规则 + * @access public + * @param mixed $filter 过滤规则 + * @return mixed + */ + public function filter($filter = null) + { + if (is_null($filter)) { + return $this->filter; + } + + $this->filter = $filter; + } + + protected function getFilter($filter, $default) + { + if (is_null($filter)) { + $filter = []; + } else { + $filter = $filter ?: $this->filter; + if (is_string($filter) && false === strpos($filter, '/')) { + $filter = explode(',', $filter); + } else { + $filter = (array) $filter; + } + } + + $filter[] = $default; + + return $filter; + } + + /** + * 递归过滤给定的值 + * @access public + * @param mixed $value 键值 + * @param mixed $key 键名 + * @param array $filters 过滤方法+默认值 + * @return mixed + */ + private function filterValue(&$value, $key, $filters) + { + $default = array_pop($filters); + + foreach ($filters as $filter) { + if (is_callable($filter)) { + // 调用函数或者方法过滤 + $value = call_user_func($filter, $value); + } elseif (is_scalar($value)) { + if (false !== strpos($filter, '/')) { + // 正则过滤 + if (!preg_match($filter, $value)) { + // 匹配不成功返回默认值 + $value = $default; + break; + } + } elseif (!empty($filter)) { + // filter函数不存在时, 则使用filter_var进行过滤 + // filter为非整形值时, 调用filter_id取得过滤id + $value = filter_var($value, is_int($filter) ? $filter : filter_id($filter)); + if (false === $value) { + $value = $default; + break; + } + } + } + } + + return $value; + } + + /** + * 强制类型转换 + * @access public + * @param string $data + * @param string $type + * @return mixed + */ + private function typeCast(&$data, $type) + { + switch (strtolower($type)) { + // 数组 + case 'a': + $data = (array) $data; + break; + // 数字 + case 'd': + $data = (int) $data; + break; + // 浮点 + case 'f': + $data = (float) $data; + break; + // 布尔 + case 'b': + $data = (boolean) $data; + break; + // 字符串 + case 's': + if (is_scalar($data)) { + $data = (string) $data; + } else { + throw new \InvalidArgumentException('variable type error:' . gettype($data)); + } + break; + } + } + + /** + * 是否存在某个请求参数 + * @access public + * @param string $name 变量名 + * @param string $type 变量类型 + * @param bool $checkEmpty 是否检测空值 + * @return mixed + */ + public function has($name, $type = 'param', $checkEmpty = false) + { + if (!in_array($type, ['param', 'get', 'post', 'request', 'put', 'file', 'session', 'cookie', 'env', 'header', 'route'])) { + return false; + } + + if (empty($this->$type)) { + $param = $this->$type(); + } else { + $param = $this->$type; + } + + // 按.拆分成多维数组进行判断 + foreach (explode('.', $name) as $val) { + if (isset($param[$val])) { + $param = $param[$val]; + } else { + return false; + } + } + + return ($checkEmpty && '' === $param) ? false : true; + } + + /** + * 获取指定的参数 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function only($name, $type = 'param') + { + $param = $this->$type(); + + if (is_string($name)) { + $name = explode(',', $name); + } + + $item = []; + foreach ($name as $key => $val) { + + if (is_int($key)) { + $default = null; + $key = $val; + } else { + $default = $val; + } + + if (isset($param[$key])) { + $item[$key] = $param[$key]; + } elseif (isset($default)) { + $item[$key] = $default; + } + } + + return $item; + } + + /** + * 排除指定参数获取 + * @access public + * @param string|array $name 变量名 + * @param string $type 变量类型 + * @return mixed + */ + public function except($name, $type = 'param') + { + $param = $this->$type(); + if (is_string($name)) { + $name = explode(',', $name); + } + + foreach ($name as $key) { + if (isset($param[$key])) { + unset($param[$key]); + } + } + + return $param; + } + + /** + * 当前是否ssl + * @access public + * @return bool + */ + public function isSsl() + { + if ($this->server('HTTPS') && ('1' == $this->server('HTTPS') || 'on' == strtolower($this->server('HTTPS')))) { + return true; + } elseif ('https' == $this->server('REQUEST_SCHEME')) { + return true; + } elseif ('443' == $this->server('SERVER_PORT')) { + return true; + } elseif ('https' == $this->server('HTTP_X_FORWARDED_PROTO')) { + return true; + } elseif ($this->config['https_agent_name'] && $this->server($this->config['https_agent_name'])) { + return true; + } + + return false; + } + + /** + * 当前是否Ajax请求 + * @access public + * @param bool $ajax true 获取原始ajax请求 + * @return bool + */ + public function isAjax($ajax = false) + { + $value = $this->server('HTTP_X_REQUESTED_WITH'); + $result = 'xmlhttprequest' == strtolower($value) ? true : false; + + if (true === $ajax) { + return $result; + } + + $result = $this->param($this->config['var_ajax']) ? true : $result; + $this->mergeParam = false; + return $result; + } + + /** + * 当前是否Pjax请求 + * @access public + * @param bool $pjax true 获取原始pjax请求 + * @return bool + */ + public function isPjax($pjax = false) + { + $result = !is_null($this->server('HTTP_X_PJAX')) ? true : false; + + if (true === $pjax) { + return $result; + } + + $result = $this->param($this->config['var_pjax']) ? true : $result; + $this->mergeParam = false; + return $result; + } + + /** + * 获取客户端IP地址 + * @access public + * @param integer $type 返回类型 0 返回IP地址 1 返回IPV4地址数字 + * @param boolean $adv 是否进行高级模式获取(有可能被伪装) + * @return mixed + */ + public function ip($type = 0, $adv = true) + { + $type = $type ? 1 : 0; + static $ip = null; + + if (null !== $ip) { + return $ip[$type]; + } + + $httpAgentIp = $this->config['http_agent_ip']; + + if ($httpAgentIp && $this->server($httpAgentIp)) { + $ip = $this->server($httpAgentIp); + } elseif ($adv) { + if ($this->server('HTTP_X_FORWARDED_FOR')) { + $arr = explode(',', $this->server('HTTP_X_FORWARDED_FOR')); + $pos = array_search('unknown', $arr); + if (false !== $pos) { + unset($arr[$pos]); + } + $ip = trim(current($arr)); + } elseif ($this->server('HTTP_CLIENT_IP')) { + $ip = $this->server('HTTP_CLIENT_IP'); + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); + } + } elseif ($this->server('REMOTE_ADDR')) { + $ip = $this->server('REMOTE_ADDR'); + } + + // IP地址类型 + $ip_mode = (strpos($ip, ':') === false) ? 'ipv4' : 'ipv6'; + + // IP地址合法验证 + if (filter_var($ip, FILTER_VALIDATE_IP) !== $ip) { + $ip = ('ipv4' === $ip_mode) ? '0.0.0.0' : '::'; + } + + // 如果是ipv4地址,则直接使用ip2long返回int类型ip;如果是ipv6地址,暂时不支持,直接返回0 + $long_ip = ('ipv4' === $ip_mode) ? sprintf("%u", ip2long($ip)) : 0; + + $ip = [$ip, $long_ip]; + + return $ip[$type]; + } + + /** + * 检测是否使用手机访问 + * @access public + * @return bool + */ + public function isMobile() + { + if ($this->server('HTTP_VIA') && stristr($this->server('HTTP_VIA'), "wap")) { + return true; + } elseif ($this->server('HTTP_ACCEPT') && strpos(strtoupper($this->server('HTTP_ACCEPT')), "VND.WAP.WML")) { + return true; + } elseif ($this->server('HTTP_X_WAP_PROFILE') || $this->server('HTTP_PROFILE')) { + return true; + } elseif ($this->server('HTTP_USER_AGENT') && preg_match('/(blackberry|configuration\/cldc|hp |hp-|htc |htc_|htc-|iemobile|kindle|midp|mmp|motorola|mobile|nokia|opera mini|opera |Googlebot-Mobile|YahooSeeker\/M1A1-R2D2|android|iphone|ipod|mobi|palm|palmos|pocket|portalmmm|ppc;|smartphone|sonyericsson|sqh|spv|symbian|treo|up.browser|up.link|vodafone|windows ce|xda |xda_)/i', $this->server('HTTP_USER_AGENT'))) { + return true; + } + + return false; + } + + /** + * 当前URL地址中的scheme参数 + * @access public + * @return string + */ + public function scheme() + { + return $this->isSsl() ? 'https' : 'http'; + } + + /** + * 当前请求URL地址中的query参数 + * @access public + * @return string + */ + public function query() + { + return $this->server('QUERY_STRING'); + } + + /** + * 设置当前请求的host(包含端口) + * @access public + * @param string $host 主机名(含端口) + * @return $this + */ + public function setHost($host) + { + $this->host = $host; + + return $this; + } + + /** + * 当前请求的host + * @access public + * @param bool $strict true 仅仅获取HOST + * @return string + */ + public function host($strict = false) + { + if (!$this->host) { + $this->host = $this->server('HTTP_X_REAL_HOST') ?: $this->server('HTTP_HOST'); + } + + return true === $strict && strpos($this->host, ':') ? strstr($this->host, ':', true) : $this->host; + } + + /** + * 当前请求URL地址中的port参数 + * @access public + * @return integer + */ + public function port() + { + return $this->server('SERVER_PORT'); + } + + /** + * 当前请求 SERVER_PROTOCOL + * @access public + * @return string + */ + public function protocol() + { + return $this->server('SERVER_PROTOCOL'); + } + + /** + * 当前请求 REMOTE_PORT + * @access public + * @return integer + */ + public function remotePort() + { + return $this->server('REMOTE_PORT'); + } + + /** + * 当前请求 HTTP_CONTENT_TYPE + * @access public + * @return string + */ + public function contentType() + { + $contentType = $this->server('CONTENT_TYPE'); + + if ($contentType) { + if (strpos($contentType, ';')) { + list($type) = explode(';', $contentType); + } else { + $type = $contentType; + } + return trim($type); + } + + return ''; + } + + /** + * 获取当前请求的路由信息 + * @access public + * @param array $route 路由名称 + * @return array + */ + public function routeInfo(array $route = []) + { + if (!empty($route)) { + $this->routeInfo = $route; + } + + return $this->routeInfo; + } + + /** + * 设置或者获取当前请求的调度信息 + * @access public + * @param \think\route\Dispatch $dispatch 调度信息 + * @return \think\route\Dispatch + */ + public function dispatch($dispatch = null) + { + if (!is_null($dispatch)) { + $this->dispatch = $dispatch; + } + + return $this->dispatch; + } + + /** + * 获取当前请求的安全Key + * @access public + * @return string + */ + public function secureKey() + { + if (is_null($this->secureKey)) { + $this->secureKey = uniqid('', true); + } + + return $this->secureKey; + } + + /** + * 设置当前的模块名 + * @access public + * @param string $module 模块名 + * @return $this + */ + public function setModule($module) + { + $this->module = $module; + return $this; + } + + /** + * 设置当前的控制器名 + * @access public + * @param string $controller 控制器名 + * @return $this + */ + public function setController($controller) + { + $this->controller = $controller; + return $this; + } + + /** + * 设置当前的操作名 + * @access public + * @param string $action 操作名 + * @return $this + */ + public function setAction($action) + { + $this->action = $action; + return $this; + } + + /** + * 获取当前的模块名 + * @access public + * @return string + */ + public function module() + { + return $this->module ?: ''; + } + + /** + * 获取当前的控制器名 + * @access public + * @param bool $convert 转换为小写 + * @return string + */ + public function controller($convert = false) + { + $name = $this->controller ?: ''; + return $convert ? strtolower($name) : $name; + } + + /** + * 获取当前的操作名 + * @access public + * @param bool $convert 转换为驼峰 + * @return string + */ + public function action($convert = false) + { + $name = $this->action ?: ''; + return $convert ? $name : strtolower($name); + } + + /** + * 设置当前的语言 + * @access public + * @param string $lang 语言名 + * @return $this + */ + public function setLangset($lang) + { + $this->langset = $lang; + return $this; + } + + /** + * 获取当前的语言 + * @access public + * @return string + */ + public function langset() + { + return $this->langset ?: ''; + } + + /** + * 设置或者获取当前请求的content + * @access public + * @return string + */ + public function getContent() + { + if (is_null($this->content)) { + $this->content = $this->input; + } + + return $this->content; + } + + /** + * 获取当前请求的php://input + * @access public + * @return string + */ + public function getInput() + { + return $this->input; + } + + /** + * 生成请求令牌 + * @access public + * @param string $name 令牌名称 + * @param mixed $type 令牌生成方法 + * @return string + */ + public function token($name = '__token__', $type = null) + { + $type = is_callable($type) ? $type : 'md5'; + $token = call_user_func($type, $this->server('REQUEST_TIME_FLOAT')); + + if ($this->isAjax()) { + header($name . ': ' . $token); + } + + facade\Session::set($name, $token); + + return $token; + } + + /** + * 设置当前地址的请求缓存 + * @access public + * @param string $key 缓存标识,支持变量规则 ,例如 item/:name/:id + * @param mixed $expire 缓存有效期 + * @param array $except 缓存排除 + * @param string $tag 缓存标签 + * @return mixed + */ + public function cache($key, $expire = null, $except = [], $tag = null) + { + if (!is_array($except)) { + $tag = $except; + $except = []; + } + + if (false === $key || !$this->isGet() || $this->isCheckCache || false === $expire) { + // 关闭当前缓存 + return; + } + + // 标记请求缓存检查 + $this->isCheckCache = true; + + foreach ($except as $rule) { + if (0 === stripos($this->url(), $rule)) { + return; + } + } + + if ($key instanceof \Closure) { + $key = call_user_func_array($key, [$this]); + } elseif (true === $key) { + // 自动缓存功能 + $key = '__URL__'; + } elseif (strpos($key, '|')) { + list($key, $fun) = explode('|', $key); + } + + // 特殊规则替换 + if (false !== strpos($key, '__')) { + $key = str_replace(['__MODULE__', '__CONTROLLER__', '__ACTION__', '__URL__'], [$this->module, $this->controller, $this->action, md5($this->url(true))], $key); + } + + if (false !== strpos($key, ':')) { + $param = $this->param(); + foreach ($param as $item => $val) { + if (is_string($val) && false !== strpos($key, ':' . $item)) { + $key = str_replace(':' . $item, $val, $key); + } + } + } elseif (strpos($key, ']')) { + if ('[' . $this->ext() . ']' == $key) { + // 缓存某个后缀的请求 + $key = md5($this->url()); + } else { + return; + } + } + + if (isset($fun)) { + $key = $fun($key); + } + + $this->cache = [$key, $expire, $tag]; + return $this->cache; + } + + /** + * 读取请求缓存设置 + * @access public + * @return array + */ + public function getCache() + { + return $this->cache; + } + + /** + * 设置GET数据 + * @access public + * @param array $get 数据 + * @return $this + */ + public function withGet(array $get) + { + $this->get = $get; + return $this; + } + + /** + * 设置POST数据 + * @access public + * @param array $post 数据 + * @return $this + */ + public function withPost(array $post) + { + $this->post = $post; + return $this; + } + + /** + * 设置php://input数据 + * @access public + * @param string $input RAW数据 + * @return $this + */ + public function withInput($input) + { + $this->input = $input; + return $this; + } + + /** + * 设置文件上传数据 + * @access public + * @param array $files 上传信息 + * @return $this + */ + public function withFiles(array $files) + { + $this->file = $files; + return $this; + } + + /** + * 设置COOKIE数据 + * @access public + * @param array $cookie 数据 + * @return $this + */ + public function withCookie(array $cookie) + { + $this->cookie = $cookie; + return $this; + } + + /** + * 设置SERVER数据 + * @access public + * @param array $server 数据 + * @return $this + */ + public function withServer(array $server) + { + $this->server = array_change_key_case($server, CASE_UPPER); + return $this; + } + + /** + * 设置HEADER数据 + * @access public + * @param array $header 数据 + * @return $this + */ + public function withHeader(array $header) + { + $this->header = array_change_key_case($header); + return $this; + } + + /** + * 设置ENV数据 + * @access public + * @param array $env 数据 + * @return $this + */ + public function withEnv(array $env) + { + $this->env = $env; + return $this; + } + + /** + * 设置ROUTE变量 + * @access public + * @param array $route 数据 + * @return $this + */ + public function withRoute(array $route) + { + $this->route = $route; + return $this; + } + + /** + * 设置请求数据 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + */ + public function __set($name, $value) + { + return $this->param[$name] = $value; + } + + /** + * 获取请求数据的值 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function __get($name) + { + return $this->param($name); + } + + /** + * 检测请求数据的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + return isset($this->param[$name]); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['dispatch'], $data['config']); + + return $data; + } +} diff --git a/thinkphp/library/think/Response.php b/thinkphp/library/think/Response.php new file mode 100644 index 0000000..5fa5402 --- /dev/null +++ b/thinkphp/library/think/Response.php @@ -0,0 +1,429 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\response\Redirect as RedirectResponse; + +class Response +{ + /** + * 原始数据 + * @var mixed + */ + protected $data; + + /** + * 应用对象实例 + * @var App + */ + protected $app; + + /** + * 当前contentType + * @var string + */ + protected $contentType = 'text/html'; + + /** + * 字符集 + * @var string + */ + protected $charset = 'utf-8'; + + /** + * 状态码 + * @var integer + */ + protected $code = 200; + + /** + * 是否允许请求缓存 + * @var bool + */ + protected $allowCache = true; + + /** + * 输出参数 + * @var array + */ + protected $options = []; + + /** + * header参数 + * @var array + */ + protected $header = []; + + /** + * 输出内容 + * @var string + */ + protected $content = null; + + /** + * 架构函数 + * @access public + * @param mixed $data 输出数据 + * @param int $code + * @param array $header + * @param array $options 输出参数 + */ + public function __construct($data = '', $code = 200, array $header = [], $options = []) + { + $this->data($data); + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->contentType($this->contentType, $this->charset); + + $this->code = $code; + $this->app = Container::get('app'); + $this->header = array_merge($this->header, $header); + } + + /** + * 创建Response对象 + * @access public + * @param mixed $data 输出数据 + * @param string $type 输出类型 + * @param int $code + * @param array $header + * @param array $options 输出参数 + * @return Response + */ + public static function create($data = '', $type = '', $code = 200, array $header = [], $options = []) + { + $class = false !== strpos($type, '\\') ? $type : '\\think\\response\\' . ucfirst(strtolower($type)); + + if (class_exists($class)) { + return new $class($data, $code, $header, $options); + } + + return new static($data, $code, $header, $options); + } + + /** + * 发送数据到客户端 + * @access public + * @return void + * @throws \InvalidArgumentException + */ + public function send() + { + // 监听response_send + $this->app['hook']->listen('response_send', $this); + + // 处理输出数据 + $data = $this->getContent(); + + // Trace调试注入 + if ('cli' != PHP_SAPI && $this->app['env']->get('app_trace', $this->app->config('app.app_trace'))) { + $this->app['debug']->inject($this, $data); + } + + if (200 == $this->code && $this->allowCache) { + $cache = $this->app['request']->getCache(); + if ($cache) { + $this->header['Cache-Control'] = 'max-age=' . $cache[1] . ',must-revalidate'; + $this->header['Last-Modified'] = gmdate('D, d M Y H:i:s') . ' GMT'; + $this->header['Expires'] = gmdate('D, d M Y H:i:s', $_SERVER['REQUEST_TIME'] + $cache[1]) . ' GMT'; + + $this->app['cache']->tag($cache[2])->set($cache[0], [$data, $this->header], $cache[1]); + } + } + + if (!headers_sent() && !empty($this->header)) { + // 发送状态码 + http_response_code($this->code); + // 发送头部信息 + foreach ($this->header as $name => $val) { + header($name . (!is_null($val) ? ':' . $val : '')); + } + } + + $this->sendData($data); + + if (function_exists('fastcgi_finish_request')) { + // 提高页面响应 + fastcgi_finish_request(); + } + + // 监听response_end + $this->app['hook']->listen('response_end', $this); + + // 清空当次请求有效的数据 + if (!($this instanceof RedirectResponse)) { + $this->app['session']->flush(); + } + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + return $data; + } + + /** + * 输出数据 + * @access protected + * @param string $data 要处理的数据 + * @return void + */ + protected function sendData($data) + { + echo $data; + } + + /** + * 输出的参数 + * @access public + * @param mixed $options 输出参数 + * @return $this + */ + public function options($options = []) + { + $this->options = array_merge($this->options, $options); + + return $this; + } + + /** + * 输出数据设置 + * @access public + * @param mixed $data 输出数据 + * @return $this + */ + public function data($data) + { + $this->data = $data; + + return $this; + } + + /** + * 是否允许请求缓存 + * @access public + * @param bool $cache 允许请求缓存 + * @return $this + */ + public function allowCache($cache) + { + $this->allowCache = $cache; + + return $this; + } + + /** + * 设置响应头 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($name, $value = null) + { + if (is_array($name)) { + $this->header = array_merge($this->header, $name); + } else { + $this->header[$name] = $value; + } + + return $this; + } + + /** + * 设置页面输出内容 + * @access public + * @param mixed $content + * @return $this + */ + public function content($content) + { + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + + return $this; + } + + /** + * 发送HTTP状态 + * @access public + * @param integer $code 状态码 + * @return $this + */ + public function code($code) + { + $this->code = $code; + + return $this; + } + + /** + * LastModified + * @access public + * @param string $time + * @return $this + */ + public function lastModified($time) + { + $this->header['Last-Modified'] = $time; + + return $this; + } + + /** + * Expires + * @access public + * @param string $time + * @return $this + */ + public function expires($time) + { + $this->header['Expires'] = $time; + + return $this; + } + + /** + * ETag + * @access public + * @param string $eTag + * @return $this + */ + public function eTag($eTag) + { + $this->header['ETag'] = $eTag; + + return $this; + } + + /** + * 页面缓存控制 + * @access public + * @param string $cache 缓存设置 + * @return $this + */ + public function cacheControl($cache) + { + $this->header['Cache-control'] = $cache; + + return $this; + } + + /** + * 设置页面不做任何缓存 + * @access public + * @return $this + */ + public function noCache() + { + $this->header['Cache-Control'] = 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'; + $this->header['Pragma'] = 'no-cache'; + + return $this; + } + + /** + * 页面输出类型 + * @access public + * @param string $contentType 输出类型 + * @param string $charset 输出编码 + * @return $this + */ + public function contentType($contentType, $charset = 'utf-8') + { + $this->header['Content-Type'] = $contentType . '; charset=' . $charset; + + return $this; + } + + /** + * 获取头部信息 + * @access public + * @param string $name 头部名称 + * @return mixed + */ + public function getHeader($name = '') + { + if (!empty($name)) { + return isset($this->header[$name]) ? $this->header[$name] : null; + } + + return $this->header; + } + + /** + * 获取原始数据 + * @access public + * @return mixed + */ + public function getData() + { + return $this->data; + } + + /** + * 获取输出数据 + * @access public + * @return mixed + */ + public function getContent() + { + if (null == $this->content) { + $content = $this->output($this->data); + + if (null !== $content && !is_string($content) && !is_numeric($content) && !is_callable([ + $content, + '__toString', + ]) + ) { + throw new \InvalidArgumentException(sprintf('variable type error: %s', gettype($content))); + } + + $this->content = (string) $content; + } + + return $this->content; + } + + /** + * 获取状态码 + * @access public + * @return integer + */ + public function getCode() + { + return $this->code; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/thinkphp/library/think/Route.php b/thinkphp/library/think/Route.php new file mode 100644 index 0000000..7bd468b --- /dev/null +++ b/thinkphp/library/think/Route.php @@ -0,0 +1,990 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\RouteNotFoundException; +use think\route\AliasRule; +use think\route\dispatch\Url as UrlDispatch; +use think\route\Domain; +use think\route\Resource; +use think\route\RuleGroup; +use think\route\RuleItem; + +class Route +{ + /** + * REST定义 + * @var array + */ + protected $rest = [ + 'index' => ['get', '', 'index'], + 'create' => ['get', '/create', 'create'], + 'edit' => ['get', '//edit', 'edit'], + 'read' => ['get', '/', 'read'], + 'save' => ['post', '', 'save'], + 'update' => ['put', '/', 'update'], + 'delete' => ['delete', '/', 'delete'], + ]; + + /** + * 请求方法前缀定义 + * @var array + */ + protected $methodPrefix = [ + 'get' => 'get', + 'post' => 'post', + 'put' => 'put', + 'delete' => 'delete', + 'patch' => 'patch', + ]; + + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 当前HOST + * @var string + */ + protected $host; + + /** + * 当前域名 + * @var string + */ + protected $domain; + + /** + * 当前分组对象 + * @var RuleGroup + */ + protected $group; + + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 路由绑定 + * @var array + */ + protected $bind = []; + + /** + * 域名对象 + * @var array + */ + protected $domains = []; + + /** + * 跨域路由规则 + * @var RuleGroup + */ + protected $cross; + + /** + * 路由别名 + * @var array + */ + protected $alias = []; + + /** + * 路由是否延迟解析 + * @var bool + */ + protected $lazy = true; + + /** + * 路由是否测试模式 + * @var bool + */ + protected $isTest; + + /** + * (分组)路由规则是否合并解析 + * @var bool + */ + protected $mergeRuleRegex = true; + + /** + * 路由解析自动搜索多级控制器 + * @var bool + */ + protected $autoSearchController = true; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->request = $app['request']; + $this->config = $config; + + $this->host = $this->request->host(true) ?: $config['app_host']; + + $this->setDefaultDomain(); + } + + public function config($name = null) + { + if (is_null($name)) { + return $this->config; + } + + return isset($this->config[$name]) ? $this->config[$name] : null; + } + + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + $config = $config->pull('app'); + $route = new static($app, $config); + + $route->lazy($config['url_lazy_route']) + ->autoSearchController($config['controller_auto_search']) + ->mergeRuleRegex($config['route_rule_merge']); + + return $route; + } + + /** + * 设置路由的请求对象实例 + * @access public + * @param Request $request 请求对象实例 + * @return void + */ + public function setRequest($request) + { + $this->request = $request; + } + + /** + * 设置路由域名及分组(包括资源路由)是否延迟解析 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + $this->lazy = $lazy; + return $this; + } + + /** + * 设置路由为测试模式 + * @access public + * @param bool $test 路由是否测试模式 + * @return void + */ + public function setTestMode($test) + { + $this->isTest = $test; + } + + /** + * 检查路由是否为测试模式 + * @access public + * @return bool + */ + public function isTest() + { + return $this->isTest; + } + + /** + * 设置路由域名及分组(包括资源路由)是否合并解析 + * @access public + * @param bool $merge 路由是否合并解析 + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + $this->mergeRuleRegex = $merge; + $this->group->mergeRuleRegex($merge); + + return $this; + } + + /** + * 设置路由自动解析是否搜索多级控制器 + * @access public + * @param bool $auto 是否自动搜索多级控制器 + * @return $this + */ + public function autoSearchController($auto = true) + { + $this->autoSearchController = $auto; + return $this; + } + + /** + * 初始化默认域名 + * @access protected + * @return void + */ + protected function setDefaultDomain() + { + // 默认域名 + $this->domain = $this->host; + + // 注册默认域名 + $domain = new Domain($this, $this->host); + + $this->domains[$this->host] = $domain; + + // 默认分组 + $this->group = $domain; + } + + /** + * 设置当前域名 + * @access public + * @param RuleGroup $group 域名 + * @return void + */ + public function setGroup(RuleGroup $group) + { + $this->group = $group; + } + + /** + * 获取当前分组 + * @access public + * @return RuleGroup + */ + public function getGroup() + { + return $this->group; + } + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + $this->group->pattern($name, $rule); + + return $this; + } + + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + $this->group->option($name, $value); + + return $this; + } + + /** + * 注册域名路由 + * @access public + * @param string|array $name 子域名 + * @param mixed $rule 路由规则 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Domain + */ + public function domain($name, $rule = '', $option = [], $pattern = []) + { + // 支持多个域名使用相同路由规则 + $domainName = is_array($name) ? array_shift($name) : $name; + + if ('*' != $domainName && false === strpos($domainName, '.')) { + $domainName .= '.' . $this->request->rootDomain(); + } + + if (!isset($this->domains[$domainName])) { + $domain = (new Domain($this, $domainName, $rule, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); + + $this->domains[$domainName] = $domain; + } else { + $domain = $this->domains[$domainName]; + $domain->parseGroupRule($rule); + } + + if (is_array($name) && !empty($name)) { + $root = $this->request->rootDomain(); + foreach ($name as $item) { + if (false === strpos($item, '.')) { + $item .= '.' . $root; + } + + $this->domains[$item] = $domainName; + } + } + + // 返回域名对象 + return $domain; + } + + /** + * 获取域名 + * @access public + * @return array + */ + public function getDomains() + { + return $this->domains; + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @param string $domain 域名 + * @return $this + */ + public function bind($bind, $domain = null) + { + $domain = is_null($domain) ? $this->domain : $domain; + + $this->bind[$domain] = $bind; + + return $this; + } + + /** + * 读取路由绑定 + * @access public + * @param string $domain 域名 + * @return string|null + */ + public function getBind($domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } elseif (true === $domain) { + return $this->bind; + } elseif (false === strpos($domain, '.')) { + $domain .= '.' . $this->request->rootDomain(); + } + + $subDomain = $this->request->subDomain(); + + if (strpos($subDomain, '.')) { + $name = '*' . strstr($subDomain, '.'); + } + + if (isset($this->bind[$domain])) { + $result = $this->bind[$domain]; + } elseif (isset($name) && isset($this->bind[$name])) { + $result = $this->bind[$name]; + } elseif (isset($this->bind['*'])) { + $result = $this->bind['*']; + } else { + $result = null; + } + + return $result; + } + + /** + * 读取路由标识 + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @return mixed + */ + public function getName($name = null, $domain = null, $method = '*') + { + return $this->app['rule_name']->get($name, $domain, $method); + } + + /** + * 读取路由 + * @access public + * @param string $rule 路由规则 + * @param string $domain 域名 + * @return array + */ + public function getRule($rule, $domain = null) + { + if (is_null($domain)) { + $domain = $this->domain; + } + + return $this->app['rule_name']->getRule($rule, $domain); + } + + /** + * 读取路由 + * @access public + * @param string $domain 域名 + * @return array + */ + public function getRuleList($domain = null) + { + return $this->app['rule_name']->getRuleList($domain); + } + + /** + * 批量导入路由标识 + * @access public + * @param array $name 路由标识 + * @return $this + */ + public function setName($name) + { + $this->app['rule_name']->import($name); + return $this; + } + + /** + * 导入配置文件的路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $type 请求类型 + * @return void + */ + public function import(array $rules, $type = '*') + { + // 检查域名部署 + if (isset($rules['__domain__'])) { + foreach ($rules['__domain__'] as $key => $rule) { + $this->domain($key, $rule); + } + unset($rules['__domain__']); + } + + // 检查变量规则 + if (isset($rules['__pattern__'])) { + $this->pattern($rules['__pattern__']); + unset($rules['__pattern__']); + } + + // 检查路由别名 + if (isset($rules['__alias__'])) { + foreach ($rules['__alias__'] as $key => $val) { + $this->alias($key, $val); + } + unset($rules['__alias__']); + } + + // 检查资源路由 + if (isset($rules['__rest__'])) { + foreach ($rules['__rest__'] as $key => $rule) { + $this->resource($key, $rule); + } + unset($rules['__rest__']); + } + + // 检查路由规则(包含分组) + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (empty($val)) { + continue; + } + + if (is_string($key) && 0 === strpos($key, '[')) { + $key = substr($key, 1, -1); + $this->group($key, $val); + } elseif (is_array($val)) { + $this->rule($key, $val[0], $type, $val[1], isset($val[2]) ? $val[2] : []); + } else { + $this->rule($key, $val, $type); + } + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function rule($rule, $route, $method = '*', array $option = [], array $pattern = []) + { + return $this->group->addRule($rule, $route, $method, $option, $pattern); + } + + /** + * 设置跨域有效路由规则 + * @access public + * @param Rule $rule 路由规则 + * @param string $method 请求类型 + * @return $this + */ + public function setCrossDomainRule($rule, $method = '*') + { + if (!isset($this->cross)) { + $this->cross = (new RuleGroup($this))->mergeRuleRegex($this->mergeRuleRegex); + } + + $this->cross->addRuleItem($rule, $method); + + return $this; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function rules($rules, $method = '*', array $option = [], array $pattern = []) + { + $this->group->addRules($rules, $method, $option, $pattern); + } + + /** + * 注册路由分组 + * @access public + * @param string|array $name 分组名称或者参数 + * @param array|\Closure $route 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup + */ + public function group($name, $route, array $option = [], array $pattern = []) + { + if (is_array($name)) { + $option = $name; + $name = isset($option['name']) ? $option['name'] : ''; + } + + return (new RuleGroup($this, $this->group, $name, $route, $option, $pattern)) + ->lazy($this->lazy) + ->mergeRuleRegex($this->mergeRuleRegex); + } + + /** + * 注册路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function any($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern); + } + + /** + * 注册GET路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function get($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'GET', $option, $pattern); + } + + /** + * 注册POST路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function post($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'POST', $option, $pattern); + } + + /** + * 注册PUT路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function put($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'PUT', $option, $pattern); + } + + /** + * 注册DELETE路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function delete($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'DELETE', $option, $pattern); + } + + /** + * 注册PATCH路由 + * @access public + * @param string $rule 路由规则 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function patch($rule, $route = '', array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, 'PATCH', $option, $pattern); + } + + /** + * 注册资源路由 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return Resource + */ + public function resource($rule, $route = '', array $option = [], array $pattern = []) + { + return (new Resource($this, $this->group, $rule, $route, $option, $pattern, $this->rest)) + ->lazy($this->lazy); + } + + /** + * 注册控制器路由 操作方法对应不同的请求前缀 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleGroup + */ + public function controller($rule, $route = '', array $option = [], array $pattern = []) + { + $group = new RuleGroup($this, $this->group, $rule, null, $option, $pattern); + + foreach ($this->methodPrefix as $type => $val) { + $group->addRule('', $val . '', $type); + } + + return $group->prefix($route . '/'); + } + + /** + * 注册视图路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $template 路由模板地址 + * @param array $vars 模板变量 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function view($rule, $template = '', array $vars = [], array $option = [], array $pattern = []) + { + return $this->rule($rule, $template, 'GET', $option, $pattern)->view($vars); + } + + /** + * 注册重定向路由 + * @access public + * @param string|array $rule 路由规则 + * @param string $route 路由地址 + * @param array $status 状态码 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return RuleItem + */ + public function redirect($rule, $route = '', $status = 301, array $option = [], array $pattern = []) + { + return $this->rule($rule, $route, '*', $option, $pattern)->redirect()->status($status); + } + + /** + * 注册别名路由 + * @access public + * @param string $rule 路由别名 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @return AliasRule + */ + public function alias($rule, $route, array $option = []) + { + $aliasRule = new AliasRule($this, $this->group, $rule, $route, $option); + + $this->alias[$rule] = $aliasRule; + + return $aliasRule; + } + + /** + * 获取别名路由定义 + * @access public + * @param string $name 路由别名 + * @return string|array|null + */ + public function getAlias($name = null) + { + if (is_null($name)) { + return $this->alias; + } + + return isset($this->alias[$name]) ? $this->alias[$name] : null; + } + + /** + * 设置不同请求类型下面的方法前缀 + * @access public + * @param string|array $method 请求类型 + * @param string $prefix 类型前缀 + * @return $this + */ + public function setMethodPrefix($method, $prefix = '') + { + if (is_array($method)) { + $this->methodPrefix = array_merge($this->methodPrefix, array_change_key_case($method)); + } else { + $this->methodPrefix[strtolower($method)] = $prefix; + } + + return $this; + } + + /** + * 获取请求类型的方法前缀 + * @access public + * @param string $method 请求类型 + * @param string $prefix 类型前缀 + * @return string|null + */ + public function getMethodPrefix($method) + { + $method = strtolower($method); + + return isset($this->methodPrefix[$method]) ? $this->methodPrefix[$method] : null; + } + + /** + * rest方法定义和修改 + * @access public + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } + + /** + * 获取rest方法定义的参数 + * @access public + * @param string $name 方法名称 + * @return array|null + */ + public function getRest($name = null) + { + if (is_null($name)) { + return $this->rest; + } + + return isset($this->rest[$name]) ? $this->rest[$name] : null; + } + + /** + * 注册未匹配路由规则后的处理 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem + */ + public function miss($route, $method = '*', array $option = []) + { + return $this->group->addMissRule($route, $method, $option); + } + + /** + * 注册一个自动解析的URL路由 + * @access public + * @param string $route 路由地址 + * @return RuleItem + */ + public function auto($route) + { + return $this->group->addAutoRule($route); + } + + /** + * 检测URL路由 + * @access public + * @param string $url URL地址 + * @param bool $must 是否强制路由 + * @return Dispatch + * @throws RouteNotFoundException + */ + public function check($url, $must = false) + { + // 自动检测域名路由 + $domain = $this->checkDomain(); + $url = str_replace($this->config['pathinfo_depr'], '|', $url); + + $completeMatch = $this->config['route_complete_match']; + + $result = $domain->check($this->request, $url, $completeMatch); + + if (false === $result && !empty($this->cross)) { + // 检测跨域路由 + $result = $this->cross->check($this->request, $url, $completeMatch); + } + + if (false !== $result) { + // 路由匹配 + return $result; + } elseif ($must) { + // 强制路由不匹配则抛出异常 + throw new RouteNotFoundException(); + } + + // 默认路由解析 + return new UrlDispatch($this->request, $this->group, $url, [ + 'auto_search' => $this->autoSearchController, + ]); + } + + /** + * 检测域名的路由规则 + * @access protected + * @return Domain + */ + protected function checkDomain() + { + // 获取当前子域名 + $subDomain = $this->request->subDomain(); + + $item = false; + + if ($subDomain && count($this->domains) > 1) { + $domain = explode('.', $subDomain); + $domain2 = array_pop($domain); + + if ($domain) { + // 存在三级域名 + $domain3 = array_pop($domain); + } + + if ($subDomain && isset($this->domains[$subDomain])) { + // 子域名配置 + $item = $this->domains[$subDomain]; + } elseif (isset($this->domains['*.' . $domain2]) && !empty($domain3)) { + // 泛三级域名 + $item = $this->domains['*.' . $domain2]; + $panDomain = $domain3; + } elseif (isset($this->domains['*']) && !empty($domain2)) { + // 泛二级域名 + if ('www' != $domain2) { + $item = $this->domains['*']; + $panDomain = $domain2; + } + } + + if (isset($panDomain)) { + // 保存当前泛域名 + $this->request->setPanDomain($panDomain); + } + } + + if (false === $item) { + // 检测当前完整域名 + $item = $this->domains[$this->host]; + } + + if (is_string($item)) { + $item = $this->domains[$item]; + } + + return $item; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear() + { + $this->app['rule_name']->clear(); + $this->group->clear(); + } + + /** + * 设置全局的路由分组参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return RuleGroup + */ + public function __call($method, $args) + { + return call_user_func_array([$this->group, $method], $args); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request']); + + return $data; + } +} diff --git a/thinkphp/library/think/Session.php b/thinkphp/library/think/Session.php new file mode 100644 index 0000000..63ee7a0 --- /dev/null +++ b/thinkphp/library/think/Session.php @@ -0,0 +1,579 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; + +class Session +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * 前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 是否初始化 + * @var bool + */ + protected $init = null; + + /** + * 锁驱动 + * @var object + */ + protected $lockDriver = null; + + /** + * 锁key + * @var string + */ + protected $sessKey = 'PHPSESSID'; + + /** + * 锁超时时间 + * @var integer + */ + protected $lockTimeout = 3; + + /** + * 是否启用锁机制 + * @var bool + */ + protected $lock = false; + + public function __construct(array $config = []) + { + $this->config = $config; + } + + /** + * 设置或者获取session作用域(前缀) + * @access public + * @param string $prefix + * @return string|void + */ + public function prefix($prefix = '') + { + empty($this->init) && $this->boot(); + + if (empty($prefix) && null !== $prefix) { + return $this->prefix; + } else { + $this->prefix = $prefix; + } + } + + public static function __make(Config $config) + { + return new static($config->pull('session')); + } + + /** + * 配置 + * @access public + * @param array $config + * @return void + */ + public function setConfig(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; + } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; + } + } + + /** + * 设置已经初始化 + * @access public + * @return void + */ + public function inited() + { + $this->init = true; + } + + /** + * session初始化 + * @access public + * @param array $config + * @return void + * @throws \think\Exception + */ + public function init(array $config = []) + { + $config = $config ?: $this->config; + + $isDoStart = false; + if (isset($config['use_trans_sid'])) { + ini_set('session.use_trans_sid', $config['use_trans_sid'] ? 1 : 0); + } + + // 启动session + if (!empty($config['auto_start']) && PHP_SESSION_ACTIVE != session_status()) { + ini_set('session.auto_start', 0); + $isDoStart = true; + } + + if (isset($config['prefix'])) { + $this->prefix = $config['prefix']; + } + + if (isset($config['use_lock'])) { + $this->lock = $config['use_lock']; + } + + if (isset($config['var_session_id']) && isset($_REQUEST[$config['var_session_id']])) { + session_id($_REQUEST[$config['var_session_id']]); + } elseif (isset($config['id']) && !empty($config['id'])) { + session_id($config['id']); + } + + if (isset($config['name'])) { + session_name($config['name']); + } + + if (isset($config['path'])) { + session_save_path($config['path']); + } + + if (isset($config['domain'])) { + ini_set('session.cookie_domain', $config['domain']); + } + + if (isset($config['expire'])) { + ini_set('session.gc_maxlifetime', $config['expire']); + ini_set('session.cookie_lifetime', $config['expire']); + } + + if (isset($config['secure'])) { + ini_set('session.cookie_secure', $config['secure']); + } + + if (isset($config['httponly'])) { + ini_set('session.cookie_httponly', $config['httponly']); + } + + if (isset($config['use_cookies'])) { + ini_set('session.use_cookies', $config['use_cookies'] ? 1 : 0); + } + + if (isset($config['cache_limiter'])) { + session_cache_limiter($config['cache_limiter']); + } + + if (isset($config['cache_expire'])) { + session_cache_expire($config['cache_expire']); + } + + if (!empty($config['type'])) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类 + if (!class_exists($class) || !session_set_save_handler(new $class($config))) { + throw new ClassNotFoundException('error session handler:' . $class, $class); + } + } + + if ($isDoStart) { + $this->start(); + } else { + $this->init = false; + } + + return $this; + } + + /** + * session自动启动或者初始化 + * @access public + * @return void + */ + public function boot() + { + if (is_null($this->init)) { + $this->init(); + } + + if (false === $this->init) { + if (PHP_SESSION_ACTIVE != session_status()) { + $this->start(); + } + $this->init = true; + } + } + + /** + * session设置 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function set($name, $value, $prefix = null) + { + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + if (strpos($name, '.')) { + // 二维数组赋值 + list($name1, $name2) = explode('.', $name); + if ($prefix) { + $_SESSION[$prefix][$name1][$name2] = $value; + } else { + $_SESSION[$name1][$name2] = $value; + } + } elseif ($prefix) { + $_SESSION[$prefix][$name] = $value; + } else { + $_SESSION[$name] = $value; + } + + $this->unlock(); + } + + /** + * session获取 + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public function get($name = '', $prefix = null) + { + $this->lock(); + + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + if ('' != $name) { + $name = explode('.', $name); + + foreach ($name as $val) { + if (isset($value[$val])) { + $value = $value[$val]; + } else { + $value = null; + break; + } + } + } + + $this->unlock(); + + return $value; + } + + /** + * session 读写锁驱动实例化 + */ + protected function initDriver() + { + $config = $this->config; + + if (!empty($config['type']) && isset($config['use_lock']) && $config['use_lock']) { + // 读取session驱动 + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\session\\driver\\' . ucwords($config['type']); + + // 检查驱动类及类中是否存在 lock 和 unlock 函数 + if (class_exists($class) && method_exists($class, 'lock') && method_exists($class, 'unlock')) { + $this->lockDriver = new $class($config); + } + } + + // 通过cookie获得session_id + if (isset($config['name']) && $config['name']) { + $this->sessKey = $config['name']; + } + + if (isset($config['lock_timeout']) && $config['lock_timeout'] > 0) { + $this->lockTimeout = $config['lock_timeout']; + } + } + + /** + * session 读写加锁 + * @access protected + * @return void + */ + protected function lock() + { + if (empty($this->lock)) { + return; + } + + $this->initDriver(); + + if (null !== $this->lockDriver && method_exists($this->lockDriver, 'lock')) { + $t = time(); + // 使用 session_id 作为互斥条件,即只对同一 session_id 的会话互斥。第一次请求没有 session_id + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + + do { + if (time() - $t > $this->lockTimeout) { + $this->unlock(); + } + } while (!$this->lockDriver->lock($sessID, $this->lockTimeout)); + } + } + + /** + * session 读写解锁 + * @access protected + * @return void + */ + protected function unlock() + { + if (empty($this->lock)) { + return; + } + + $this->pause(); + + if ($this->lockDriver && method_exists($this->lockDriver, 'unlock')) { + $sessID = isset($_COOKIE[$this->sessKey]) ? $_COOKIE[$this->sessKey] : ''; + $this->lockDriver->unlock($sessID); + } + } + + /** + * session获取并删除 + * @access public + * @param string $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return mixed + */ + public function pull($name, $prefix = null) + { + $result = $this->get($name, $prefix); + + if ($result) { + $this->delete($name, $prefix); + return $result; + } else { + return; + } + } + + /** + * session设置 下一次请求有效 + * @access public + * @param string $name session名称 + * @param mixed $value session值 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function flash($name, $value) + { + $this->set($name, $value); + + if (!$this->has('__flash__.__time__')) { + $this->set('__flash__.__time__', $_SERVER['REQUEST_TIME_FLOAT']); + } + + $this->push('__flash__', $name); + } + + /** + * 清空当前请求的session数据 + * @access public + * @return void + */ + public function flush() + { + if (!$this->init) { + return; + } + + $item = $this->get('__flash__'); + + if (!empty($item)) { + $time = $item['__time__']; + + if ($_SERVER['REQUEST_TIME_FLOAT'] > $time) { + unset($item['__time__']); + $this->delete($item); + $this->set('__flash__', []); + } + } + } + + /** + * 删除session数据 + * @access public + * @param string|array $name session名称 + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function delete($name, $prefix = null) + { + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + if (is_array($name)) { + foreach ($name as $key) { + $this->delete($key, $prefix); + } + } elseif (strpos($name, '.')) { + list($name1, $name2) = explode('.', $name); + if ($prefix) { + unset($_SESSION[$prefix][$name1][$name2]); + } else { + unset($_SESSION[$name1][$name2]); + } + } else { + if ($prefix) { + unset($_SESSION[$prefix][$name]); + } else { + unset($_SESSION[$name]); + } + } + } + + /** + * 清空session数据 + * @access public + * @param string|null $prefix 作用域(前缀) + * @return void + */ + public function clear($prefix = null) + { + empty($this->init) && $this->boot(); + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + + if ($prefix) { + unset($_SESSION[$prefix]); + } else { + $_SESSION = []; + } + } + + /** + * 判断session数据 + * @access public + * @param string $name session名称 + * @param string|null $prefix + * @return bool + */ + public function has($name, $prefix = null) + { + empty($this->init) && $this->boot(); + + $prefix = !is_null($prefix) ? $prefix : $this->prefix; + $value = $prefix ? (!empty($_SESSION[$prefix]) ? $_SESSION[$prefix] : []) : $_SESSION; + + $name = explode('.', $name); + + foreach ($name as $val) { + if (!isset($value[$val])) { + return false; + } else { + $value = $value[$val]; + } + } + + return true; + } + + /** + * 添加数据到一个session数组 + * @access public + * @param string $key + * @param mixed $value + * @return void + */ + public function push($key, $value) + { + $array = $this->get($key); + + if (is_null($array)) { + $array = []; + } + + $array[] = $value; + + $this->set($key, $array); + } + + /** + * 启动session + * @access public + * @return void + */ + public function start() + { + session_start(); + + $this->init = true; + } + + /** + * 销毁session + * @access public + * @return void + */ + public function destroy() + { + if (!empty($_SESSION)) { + $_SESSION = []; + } + + session_unset(); + session_destroy(); + + $this->init = null; + $this->lockDriver = null; + } + + /** + * 重新生成session_id + * @access public + * @param bool $delete 是否删除关联会话文件 + * @return void + */ + public function regenerate($delete = false) + { + session_regenerate_id($delete); + } + + /** + * 暂停session + * @access public + * @return void + */ + public function pause() + { + // 暂停session + session_write_close(); + $this->init = false; + } +} diff --git a/thinkphp/library/think/Template.php b/thinkphp/library/think/Template.php new file mode 100644 index 0000000..dc1acfa --- /dev/null +++ b/thinkphp/library/think/Template.php @@ -0,0 +1,1318 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\TemplateNotFoundException; + +/** + * ThinkPHP分离出来的模板引擎 + * 支持XML标签和普通标签的模板解析 + * 编译型模板引擎 支持动态缓存 + */ +class Template +{ + protected $app; + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 模板配置参数 + * @var array + */ + protected $config = [ + 'view_path' => '', // 模板路径 + 'view_base' => '', + 'view_suffix' => 'html', // 默认模板文件后缀 + 'view_depr' => DIRECTORY_SEPARATOR, + 'cache_suffix' => 'php', // 默认模板缓存后缀 + 'tpl_deny_func_list' => 'echo,exit', // 模板引擎禁用函数 + 'tpl_deny_php' => false, // 默认模板引擎是否禁用PHP原生代码 + 'tpl_begin' => '{', // 模板引擎普通标签开始标记 + 'tpl_end' => '}', // 模板引擎普通标签结束标记 + 'strip_space' => false, // 是否去除模板文件里面的html空格与换行 + 'tpl_cache' => true, // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'compile_type' => 'file', // 模板编译类型 + 'cache_prefix' => '', // 模板缓存前缀标识,可以动态改变 + 'cache_time' => 0, // 模板缓存有效期 0 为永久,(以数字为值,单位:秒) + 'layout_on' => false, // 布局模板开关 + 'layout_name' => 'layout', // 布局模板入口文件 + 'layout_item' => '{__CONTENT__}', // 布局模板的内容替换标识 + 'taglib_begin' => '{', // 标签库标签开始标记 + 'taglib_end' => '}', // 标签库标签结束标记 + 'taglib_load' => true, // 是否使用内置标签库之外的其它标签库,默认自动检测 + 'taglib_build_in' => 'cx', // 内置标签库名称(标签使用不必指定标签库名称),以逗号分隔 注意解析顺序 + 'taglib_pre_load' => '', // 需要额外加载的标签库(须指定标签库名称),多个以逗号分隔 + 'display_cache' => false, // 模板渲染缓存 + 'cache_id' => '', // 模板缓存ID + 'tpl_replace_string' => [], + 'tpl_var_identify' => 'array', // .语法变量识别,array|object|'', 为空时自动识别 + 'default_filter' => 'htmlentities', // 默认过滤方法 用于普通标签输出 + ]; + + /** + * 保留内容信息 + * @var array + */ + private $literal = []; + + /** + * 模板包含信息 + * @var array + */ + private $includeFile = []; + + /** + * 模板存储对象 + * @var object + */ + protected $storage; + + /** + * 架构函数 + * @access public + * @param array $config + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config['cache_path'] = $app->getRuntimePath() . 'temp/'; + $this->config = array_merge($this->config, $config); + + $this->config['taglib_begin_origin'] = $this->config['taglib_begin']; + $this->config['taglib_end_origin'] = $this->config['taglib_end']; + + $this->config['taglib_begin'] = preg_quote($this->config['taglib_begin'], '/'); + $this->config['taglib_end'] = preg_quote($this->config['taglib_end'], '/'); + $this->config['tpl_begin'] = preg_quote($this->config['tpl_begin'], '/'); + $this->config['tpl_end'] = preg_quote($this->config['tpl_end'], '/'); + + // 初始化模板编译存储器 + $type = $this->config['compile_type'] ? $this->config['compile_type'] : 'File'; + + $this->storage = Loader::factory($type, '\\think\\template\\driver\\', null); + } + + public static function __make(Config $config) + { + return new static($config->pull('template')); + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name + * @param mixed $value + * @return void + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + } + + /** + * 模板引擎参数赋值 + * @access public + * @param mixed $name + * @param mixed $value + */ + public function __set($name, $value) + { + $this->config[$name] = $value; + } + + /** + * 模板引擎配置项 + * @access public + * @param array|string $config + * @return void|array + */ + public function config($config) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } elseif (isset($this->config[$config])) { + return $this->config[$config]; + } + } + + /** + * 模板变量获取 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function get($name = '') + { + if ('' == $name) { + return $this->data; + } + + $data = $this->data; + + foreach (explode('.', $name) as $key => $val) { + if (isset($data[$val])) { + $data = $data[$val]; + } else { + $data = null; + break; + } + } + + return $data; + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + + if ($config) { + $this->config($config); + } + + $cache = $this->app['cache']; + + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 读取渲染缓存 + $cacheContent = $cache->get($this->config['cache_id']); + + if (false !== $cacheContent) { + echo $cacheContent; + return; + } + } + + $template = $this->parseTemplateFile($template); + + if ($template) { + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($this->config['layout_on'] . $this->config['layout_name'] . $template) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 重新模板编译 + $content = file_get_contents($template); + $this->compiler($content, $cacheFile); + } + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + + // 获取并清空缓存 + $content = ob_get_clean(); + + if (!empty($this->config['cache_id']) && $this->config['display_cache']) { + // 缓存页面输出 + $cache->set($this->config['cache_id'], $content, $this->config['cache_time']); + } + + echo $content; + } + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $vars 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($content, $vars = [], $config = []) + { + if ($vars) { + $this->data = $vars; + } + + if ($config) { + $this->config($config); + } + + $cacheFile = $this->config['cache_path'] . $this->config['cache_prefix'] . md5($content) . '.' . ltrim($this->config['cache_suffix'], '.'); + + if (!$this->checkCache($cacheFile)) { + // 缓存无效 模板编译 + $this->compiler($content, $cacheFile); + } + + // 读取编译存储 + $this->storage->read($cacheFile, $this->data); + } + + /** + * 设置布局 + * @access public + * @param mixed $name 布局模板名称 false 则关闭布局 + * @param string $replace 布局模板内容替换标识 + * @return object + */ + public function layout($name, $replace = '') + { + if (false === $name) { + // 关闭布局 + $this->config['layout_on'] = false; + } else { + // 开启布局 + $this->config['layout_on'] = true; + + // 名称必须为字符串 + if (is_string($name)) { + $this->config['layout_name'] = $name; + } + + if (!empty($replace)) { + $this->config['layout_item'] = $replace; + } + } + + return $this; + } + + /** + * 检查编译缓存是否有效 + * 如果无效则需要重新编译 + * @access private + * @param string $cacheFile 缓存文件名 + * @return boolean + */ + private function checkCache($cacheFile) + { + if (!$this->config['tpl_cache'] || !is_file($cacheFile) || !$handle = @fopen($cacheFile, "r")) { + return false; + } + + // 读取第一行 + preg_match('/\/\*(.+?)\*\//', fgets($handle), $matches); + + if (!isset($matches[1])) { + return false; + } + + $includeFile = unserialize($matches[1]); + + if (!is_array($includeFile)) { + return false; + } + + // 检查模板文件是否有更新 + foreach ($includeFile as $path => $time) { + if (is_file($path) && filemtime($path) > $time) { + // 模板文件如果有更新则缓存需要更新 + return false; + } + } + + // 检查编译存储是否有效 + return $this->storage->check($cacheFile, $this->config['cache_time']); + } + + /** + * 检查编译缓存是否存在 + * @access public + * @param string $cacheId 缓存的id + * @return boolean + */ + public function isCache($cacheId) + { + if ($cacheId && $this->config['display_cache']) { + // 缓存页面输出 + return $this->app['cache']->has($cacheId); + } + + return false; + } + + /** + * 编译模板文件内容 + * @access private + * @param string $content 模板内容 + * @param string $cacheFile 缓存文件名 + * @return void + */ + private function compiler(&$content, $cacheFile) + { + // 判断是否启用布局 + if ($this->config['layout_on']) { + if (false !== strpos($content, '{__NOLAYOUT__}')) { + // 可以单独定义不使用布局 + $content = str_replace('{__NOLAYOUT__}', '', $content); + } else { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($this->config['layout_name']); + + if ($layoutFile) { + // 替换布局的主体内容 + $content = str_replace($this->config['layout_item'], $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + + // 模板解析 + $this->parse($content); + + if ($this->config['strip_space']) { + /* 去除html空格与换行 */ + $find = ['~>\s+<~', '~>(\s+\n|\r)~']; + $replace = ['><', '>']; + $content = preg_replace($find, $replace, $content); + } + + // 优化生成的php代码 + $content = preg_replace('/\?>\s*<\?php\s(?!echo\b|\bend)/s', '', $content); + + // 模板过滤输出 + $replace = $this->config['tpl_replace_string']; + $content = str_replace(array_keys($replace), array_values($replace), $content); + + // 添加安全代码及模板引用记录 + $content = 'includeFile) . '*/ ?>' . "\n" . $content; + // 编译存储 + $this->storage->write($cacheFile, $content); + + $this->includeFile = []; + } + + /** + * 模板解析入口 + * 支持普通标签和TagLib解析 支持自定义标签库 + * @access public + * @param string $content 要解析的模板内容 + * @return void + */ + public function parse(&$content) + { + // 内容为空不解析 + if (empty($content)) { + return; + } + + // 替换literal标签内容 + $this->parseLiteral($content); + + // 解析继承 + $this->parseExtend($content); + + // 解析布局 + $this->parseLayout($content); + + // 检查include语法 + $this->parseInclude($content); + + // 替换包含文件中literal标签内容 + $this->parseLiteral($content); + + // 检查PHP语法 + $this->parsePhp($content); + + // 获取需要引入的标签库列表 + // 标签库只需要定义一次,允许引入多个一次 + // 一般放在文件的最前面 + // 格式: + // 当TAGLIB_LOAD配置为true时才会进行检测 + if ($this->config['taglib_load']) { + $tagLibs = $this->getIncludeTagLib($content); + + if (!empty($tagLibs)) { + // 对导入的TagLib进行解析 + foreach ($tagLibs as $tagLibName) { + $this->parseTagLib($tagLibName, $content); + } + } + } + + // 预先加载的标签库 无需在每个模板中使用taglib标签加载 但必须使用标签库XML前缀 + if ($this->config['taglib_pre_load']) { + $tagLibs = explode(',', $this->config['taglib_pre_load']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content); + } + } + + // 内置标签库 无需使用taglib标签导入就可以使用 并且不需使用标签库XML前缀 + $tagLibs = explode(',', $this->config['taglib_build_in']); + + foreach ($tagLibs as $tag) { + $this->parseTagLib($tag, $content, true); + } + + // 解析普通模板标签 {$tagName} + $this->parseTag($content); + + // 还原被替换的Literal标签 + $this->parseLiteral($content, true); + } + + /** + * 检查PHP语法 + * @access private + * @param string $content 要解析的模板内容 + * @return void + * @throws \think\Exception + */ + private function parsePhp(&$content) + { + // 短标签的情况要将' . "\n", $content); + + // PHP语法检查 + if ($this->config['tpl_deny_php'] && false !== strpos($content, 'getRegex('layout'), $content, $matches)) { + // 替换Layout标签 + $content = str_replace($matches[0], '', $content); + // 解析Layout标签 + $array = $this->parseAttr($matches[0]); + + if (!$this->config['layout_on'] || $this->config['layout_name'] != $array['name']) { + // 读取布局模板 + $layoutFile = $this->parseTemplateFile($array['name']); + + if ($layoutFile) { + $replace = isset($array['replace']) ? $array['replace'] : $this->config['layout_item']; + // 替换布局的主体内容 + $content = str_replace($replace, $content, file_get_contents($layoutFile)); + } + } + } else { + $content = str_replace('{__NOLAYOUT__}', '', $content); + } + } + + /** + * 解析模板中的include标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseInclude(&$content) + { + $regex = $this->getRegex('include'); + $func = function ($template) use (&$func, &$regex, &$content) { + if (preg_match_all($regex, $template, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array = $this->parseAttr($match[0]); + $file = $array['file']; + unset($array['file']); + + // 分析模板文件名并读取内容 + $parseStr = $this->parseTemplateName($file); + + foreach ($array as $k => $v) { + // 以$开头字符串转换成模板变量 + if (0 === strpos($v, '$')) { + $v = $this->get(substr($v, 1)); + } + + $parseStr = str_replace('[' . $k . ']', $v, $parseStr); + } + + $content = str_replace($match[0], $parseStr, $content); + // 再次对包含文件进行模板分析 + $func($parseStr); + } + unset($matches); + } + }; + + // 替换模板中的include标签 + $func($content); + } + + /** + * 解析模板中的extend标签 + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseExtend(&$content) + { + $regex = $this->getRegex('extend'); + $array = $blocks = $baseBlocks = []; + $extend = ''; + + $func = function ($template) use (&$func, &$regex, &$array, &$extend, &$blocks, &$baseBlocks) { + if (preg_match($regex, $template, $matches)) { + if (!isset($array[$matches['name']])) { + $array[$matches['name']] = 1; + // 读取继承模板 + $extend = $this->parseTemplateName($matches['name']); + + // 递归检查继承 + $func($extend); + + // 取得block标签内容 + $blocks = array_merge($blocks, $this->parseBlock($template)); + + return; + } + } else { + // 取得顶层模板block标签内容 + $baseBlocks = $this->parseBlock($template, true); + + if (empty($extend)) { + // 无extend标签但有block标签的情况 + $extend = $template; + } + } + }; + + $func($content); + + if (!empty($extend)) { + if ($baseBlocks) { + $children = []; + foreach ($baseBlocks as $name => $val) { + $replace = $val['content']; + + if (!empty($children[$name])) { + // 如果包含有子block标签 + foreach ($children[$name] as $key) { + $replace = str_replace($baseBlocks[$key]['begin'] . $baseBlocks[$key]['content'] . $baseBlocks[$key]['end'], $blocks[$key]['content'], $replace); + } + } + + if (isset($blocks[$name])) { + // 带有{__block__}表示与所继承模板的相应标签合并,而不是覆盖 + $replace = str_replace(['{__BLOCK__}', '{__block__}'], $replace, $blocks[$name]['content']); + + if (!empty($val['parent'])) { + // 如果不是最顶层的block标签 + $parent = $val['parent']; + + if (isset($blocks[$parent])) { + $blocks[$parent]['content'] = str_replace($blocks[$name]['begin'] . $blocks[$name]['content'] . $blocks[$name]['end'], $replace, $blocks[$parent]['content']); + } + + $blocks[$name]['content'] = $replace; + $children[$parent][] = $name; + + continue; + } + } elseif (!empty($val['parent'])) { + // 如果子标签没有被继承则用原值 + $children[$val['parent']][] = $name; + $blocks[$name] = $val; + } + + if (!$val['parent']) { + // 替换模板中的顶级block标签 + $extend = str_replace($val['begin'] . $val['content'] . $val['end'], $replace, $extend); + } + } + } + + $content = $extend; + unset($blocks, $baseBlocks); + } + } + + /** + * 替换页面中的literal标签 + * @access private + * @param string $content 模板内容 + * @param boolean $restore 是否为还原 + * @return void + */ + private function parseLiteral(&$content, $restore = false) + { + $regex = $this->getRegex($restore ? 'restoreliteral' : 'literal'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + if (!$restore) { + $count = count($this->literal); + + // 替换literal标签 + foreach ($matches as $match) { + $this->literal[] = substr($match[0], strlen($match[1]), -strlen($match[2])); + $content = str_replace($match[0], "", $content); + $count++; + } + } else { + // 还原literal标签 + foreach ($matches as $match) { + $content = str_replace($match[0], $this->literal[$match[1]], $content); + } + + // 清空literal记录 + $this->literal = []; + } + + unset($matches); + } + } + + /** + * 获取模板中的block标签 + * @access private + * @param string $content 模板内容 + * @param boolean $sort 是否排序 + * @return array + */ + private function parseBlock(&$content, $sort = false) + { + $regex = $this->getRegex('block'); + $result = []; + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = $keys = []; + + foreach ($matches as $match) { + if (empty($match['name'][0])) { + if (count($right) > 0) { + $tag = array_pop($right); + $start = $tag['offset'] + strlen($tag['tag']); + $length = $match[0][1] - $start; + + $result[$tag['name']] = [ + 'begin' => $tag['tag'], + 'content' => substr($content, $start, $length), + 'end' => $match[0][0], + 'parent' => count($right) ? end($right)['name'] : '', + ]; + + $keys[$tag['name']] = $match[0][1]; + } + } else { + // 标签头压入栈 + $right[] = [ + 'name' => $match[2][0], + 'offset' => $match[0][1], + 'tag' => $match[0][0], + ]; + } + } + + unset($right, $matches); + + if ($sort) { + // 按block标签结束符在模板中的位置排序 + array_multisort($keys, $result); + } + } + + return $result; + } + + /** + * 搜索模板页面中包含的TagLib库 + * 并返回列表 + * @access private + * @param string $content 模板内容 + * @return array|null + */ + private function getIncludeTagLib(&$content) + { + // 搜索是否有TagLib标签 + if (preg_match($this->getRegex('taglib'), $content, $matches)) { + // 替换TagLib标签 + $content = str_replace($matches[0], '', $content); + + return explode(',', $matches['name']); + } + } + + /** + * TagLib库解析 + * @access public + * @param string $tagLib 要解析的标签库 + * @param string $content 要解析的模板内容 + * @param boolean $hide 是否隐藏标签库前缀 + * @return void + */ + public function parseTagLib($tagLib, &$content, $hide = false) + { + if (false !== strpos($tagLib, '\\')) { + // 支持指定标签库的命名空间 + $className = $tagLib; + $tagLib = substr($tagLib, strrpos($tagLib, '\\') + 1); + } else { + $className = '\\think\\template\\taglib\\' . ucwords($tagLib); + } + + $tLib = new $className($this); + + $tLib->parseTag($content, $hide ? '' : $tagLib); + } + + /** + * 分析标签属性 + * @access public + * @param string $str 属性字符串 + * @param string $name 不为空时返回指定的属性名 + * @return array + */ + public function parseAttr($str, $name = null) + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $array = []; + + if (preg_match_all($regex, $str, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $array[$match['name']] = $match['value']; + } + unset($matches); + } + + if (!empty($name) && isset($array[$name])) { + return $array[$name]; + } + + return $array; + } + + /** + * 模板标签解析 + * 格式: {TagName:args [|content] } + * @access private + * @param string $content 要解析的模板内容 + * @return void + */ + private function parseTag(&$content) + { + $regex = $this->getRegex('tag'); + + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER)) { + foreach ($matches as $match) { + $str = stripslashes($match[1]); + $flag = substr($str, 0, 1); + + switch ($flag) { + case '$': + // 解析模板变量 格式 {$varName} + // 是否带有?号 + if (false !== $pos = strpos($str, '?')) { + $array = preg_split('/([!=]={1,2}|(?<]={0,1})/', substr($str, 0, $pos), 2, PREG_SPLIT_DELIM_CAPTURE); + $name = $array[0]; + + $this->parseVar($name); + //$this->parseVarFunction($name); + + $str = trim(substr($str, $pos + 1)); + $this->parseVar($str); + $first = substr($str, 0, 1); + + if (strpos($name, ')')) { + // $name为对象或是自动识别,或者含有函数 + if (isset($array[1])) { + $this->parseVar($array[2]); + $name .= $array[1] . $array[2]; + } + + switch ($first) { + case '?': + $this->parseVarFunction($name); + $str = ''; + break; + case '=': + $str = ''; + break; + default: + $str = ''; + } + } else { + if (isset($array[1])) { + $express = true; + $this->parseVar($array[2]); + $express = $name . $array[1] . $array[2]; + } else { + $express = false; + } + + if (in_array($first, ['?', '=', ':'])) { + $str = trim(substr($str, 1)); + if ('$' == substr($str, 0, 1)) { + $str = $this->parseVarFunction($str); + } + } + + // $name为数组 + switch ($first) { + case '?': + // {$varname??'xxx'} $varname有定义则输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + case '=': + // {$varname?='xxx'} $varname为真时才输出xxx + $str = ''; + break; + case ':': + // {$varname?:'xxx'} $varname为真时输出$varname,否则输出xxx + $str = 'parseVarFunction($name) . ' : ' . $str . '; ?>'; + break; + default: + if (strpos($str, ':')) { + // {$varname ? 'a' : 'b'} $varname为真时输出a,否则输出b + $array = explode(':', $str, 2); + + $array[0] = '$' == substr(trim($array[0]), 0, 1) ? $this->parseVarFunction($array[0]) : $array[0]; + $array[1] = '$' == substr(trim($array[1]), 0, 1) ? $this->parseVarFunction($array[1]) : $array[1]; + + $str = implode(' : ', $array); + } + $str = ''; + } + } + } else { + $this->parseVar($str); + $this->parseVarFunction($str); + $str = ''; + } + break; + case ':': + // 输出某个函数的结果 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '~': + // 执行某个函数 + $str = substr($str, 1); + $this->parseVar($str); + $str = ''; + break; + case '-': + case '+': + // 输出计算 + $this->parseVar($str); + $str = ''; + break; + case '/': + // 注释标签 + $flag2 = substr($str, 1, 1); + if ('/' == $flag2 || ('*' == $flag2 && substr(rtrim($str), -2) == '*/')) { + $str = ''; + } + break; + default: + // 未识别的标签直接返回 + $str = $this->config['tpl_begin'] . $str . $this->config['tpl_end']; + break; + } + + $content = str_replace($match[0], $str, $content); + } + + unset($matches); + } + } + + /** + * 模板变量解析,支持使用函数 + * 格式: {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量数据 + * @return void + */ + public function parseVar(&$varStr) + { + $varStr = trim($varStr); + + if (preg_match_all('/\$[a-zA-Z_](?>\w*)(?:[:\.][0-9a-zA-Z_](?>\w*))+/', $varStr, $matches, PREG_OFFSET_CAPTURE)) { + static $_varParseList = []; + + while ($matches[0]) { + $match = array_pop($matches[0]); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varParseList[$match[0]])) { + $parseStr = $_varParseList[$match[0]]; + } else { + if (strpos($match[0], '.')) { + $vars = explode('.', $match[0]); + $first = array_shift($vars); + + if ('$Think' == $first) { + // 所有以Think.打头的以特殊变量对待 无需模板赋值就可以输出 + $parseStr = $this->parseThinkVar($vars); + } elseif ('$Request' == $first) { + // 获取Request请求对象参数 + $method = array_shift($vars); + if (!empty($vars)) { + $params = implode('.', $vars); + if ('true' != $params) { + $params = '\'' . $params . '\''; + } + } else { + $params = ''; + } + + $parseStr = 'app(\'request\')->' . $method . '(' . $params . ')'; + } else { + switch ($this->config['tpl_var_identify']) { + case 'array': // 识别为数组 + $parseStr = $first . '[\'' . implode('\'][\'', $vars) . '\']'; + break; + case 'obj': // 识别为对象 + $parseStr = $first . '->' . implode('->', $vars); + break; + default: // 自动判断数组或对象 + $parseStr = '(is_array(' . $first . ')?' . $first . '[\'' . implode('\'][\'', $vars) . '\']:' . $first . '->' . implode('->', $vars) . ')'; + } + } + } else { + $parseStr = str_replace(':', '->', $match[0]); + } + + $_varParseList[$match[0]] = $parseStr; + } + + $varStr = substr_replace($varStr, $parseStr, $match[1], strlen($match[0])); + } + unset($matches); + } + } + + /** + * 对模板中使用了函数的变量进行解析 + * 格式 {$varname|function1|function2=arg1,arg2} + * @access public + * @param string $varStr 变量字符串 + * @param bool $autoescape 自动转义 + * @return void + */ + public function parseVarFunction(&$varStr, $autoescape = true) + { + if (!$autoescape && false === strpos($varStr, '|')) { + return $varStr; + } elseif ($autoescape && !preg_match('/\|(\s)?raw(\||\s)?/i', $varStr)) { + $varStr .= '|' . $this->config['default_filter']; + } + + static $_varFunctionList = []; + + $_key = md5($varStr); + + //如果已经解析过该变量字串,则直接返回变量值 + if (isset($_varFunctionList[$_key])) { + $varStr = $_varFunctionList[$_key]; + } else { + $varArray = explode('|', $varStr); + + // 取得变量名称 + $name = trim(array_shift($varArray)); + + // 对变量使用函数 + $length = count($varArray); + + // 取得模板禁止使用函数列表 + $template_deny_funs = explode(',', $this->config['tpl_deny_func_list']); + + for ($i = 0; $i < $length; $i++) { + $args = explode('=', $varArray[$i], 2); + + // 模板函数过滤 + $fun = trim($args[0]); + if (in_array($fun, $template_deny_funs)) { + continue; + } + + switch (strtolower($fun)) { + case 'raw': + break; + case 'date': + $name = 'date(' . $args[1] . ',!is_numeric(' . $name . ')? strtotime(' . $name . ') : ' . $name . ')'; + break; + case 'first': + $name = 'current(' . $name . ')'; + break; + case 'last': + $name = 'end(' . $name . ')'; + break; + case 'upper': + $name = 'strtoupper(' . $name . ')'; + break; + case 'lower': + $name = 'strtolower(' . $name . ')'; + break; + case 'format': + $name = 'sprintf(' . $args[1] . ',' . $name . ')'; + break; + case 'default': // 特殊模板函数 + if (false === strpos($name, '(')) { + $name = '(isset(' . $name . ') && (' . $name . ' !== \'\')?' . $name . ':' . $args[1] . ')'; + } else { + $name = '(' . $name . ' ?: ' . $args[1] . ')'; + } + break; + default: // 通用模板函数 + if (isset($args[1])) { + if (strstr($args[1], '###')) { + $args[1] = str_replace('###', $name, $args[1]); + $name = "$fun($args[1])"; + } else { + $name = "$fun($name,$args[1])"; + } + } else { + if (!empty($args[0])) { + $name = "$fun($name)"; + } + } + } + } + + $_varFunctionList[$_key] = $name; + $varStr = $name; + } + return $varStr; + } + + /** + * 特殊模板变量解析 + * 格式 以 $Think. 打头的变量属于特殊模板变量 + * @access public + * @param array $vars 变量数组 + * @return string + */ + public function parseThinkVar($vars) + { + $type = strtoupper(trim(array_shift($vars))); + $param = implode('.', $vars); + + if ($vars) { + switch ($type) { + case 'SERVER': + $parseStr = 'app(\'request\')->server(\'' . $param . '\')'; + break; + case 'GET': + $parseStr = 'app(\'request\')->get(\'' . $param . '\')'; + break; + case 'POST': + $parseStr = 'app(\'request\')->post(\'' . $param . '\')'; + break; + case 'COOKIE': + $parseStr = 'app(\'cookie\')->get(\'' . $param . '\')'; + break; + case 'SESSION': + $parseStr = 'app(\'session\')->get(\'' . $param . '\')'; + break; + case 'ENV': + $parseStr = 'app(\'request\')->env(\'' . $param . '\')'; + break; + case 'REQUEST': + $parseStr = 'app(\'request\')->request(\'' . $param . '\')'; + break; + case 'CONST': + $parseStr = strtoupper($param); + break; + case 'LANG': + $parseStr = 'app(\'lang\')->get(\'' . $param . '\')'; + break; + case 'CONFIG': + $parseStr = 'app(\'config\')->get(\'' . $param . '\')'; + break; + default: + $parseStr = '\'\''; + break; + } + } else { + switch ($type) { + case 'NOW': + $parseStr = "date('Y-m-d g:i a',time())"; + break; + case 'VERSION': + $parseStr = 'app()->version()'; + break; + case 'LDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_begin'], '\\') . '\''; + break; + case 'RDELIM': + $parseStr = '\'' . ltrim($this->config['tpl_end'], '\\') . '\''; + break; + default: + if (defined($type)) { + $parseStr = $type; + } else { + $parseStr = ''; + } + } + } + + return $parseStr; + } + + /** + * 分析加载的模板文件并读取内容 支持多个模板文件读取 + * @access private + * @param string $templateName 模板文件名 + * @return string + */ + private function parseTemplateName($templateName) + { + $array = explode(',', $templateName); + $parseStr = ''; + + foreach ($array as $templateName) { + if (empty($templateName)) { + continue; + } + + if (0 === strpos($templateName, '$')) { + //支持加载变量文件名 + $templateName = $this->get(substr($templateName, 1)); + } + + $template = $this->parseTemplateFile($templateName); + + if ($template) { + // 获取模板文件内容 + $parseStr .= file_get_contents($template); + } + } + + return $parseStr; + } + + /** + * 解析模板文件名 + * @access private + * @param string $template 文件名 + * @return string|false + */ + private function parseTemplateFile($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + if (strpos($template, '@')) { + list($module, $template) = explode('@', $template); + } + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $this->config['view_depr'], $template); + } else { + $template = str_replace(['/', ':'], $this->config['view_depr'], substr($template, 1)); + } + + if ($this->config['view_base']) { + $module = isset($module) ? $module : $this->app['request']->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); + } else { + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . basename($this->config['view_path']) . DIRECTORY_SEPARATOR : $this->config['view_path']; + } + + $template = $path . $template . '.' . ltrim($this->config['view_suffix'], '.'); + } + + if (is_file($template)) { + // 记录模板文件的更新时间 + $this->includeFile[$template] = filemtime($template); + + return $template; + } + + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + /** + * 按标签生成正则 + * @access private + * @param string $tagName 标签名 + * @return string + */ + private function getRegex($tagName) + { + $regex = ''; + if ('tag' == $tagName) { + $begin = $this->config['tpl_begin']; + $end = $this->config['tpl_end']; + + if (strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1) { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>[^' . $end . ']*))' . $end; + } else { + $regex = $begin . '((?:[\$]{1,2}[a-wA-w_]|[\:\~][\$a-wA-w_]|[+]{2}[\$][a-wA-w_]|[-]{2}[\$][a-wA-w_]|\/[\*\/])(?>(?:(?!' . $end . ').)*))' . $end; + } + } else { + $begin = $this->config['taglib_begin']; + $end = $this->config['taglib_end']; + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + + switch ($tagName) { + case 'block': + if ($single) { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>[^' . $end . ']*)|\/' . $tagName . ')' . $end; + } else { + $regex = $begin . '(?:' . $tagName . '\b\s+(?>(?:(?!name=).)*)\bname=([\'\"])(?P[\$\w\-\/\.]+)\\1(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end; + } + break; + case 'literal': + if ($single) { + $regex = '(' . $begin . $tagName . '\b(?>[^' . $end . ']*)' . $end . ')'; + $regex .= '(?:(?>[^' . $begin . ']*)(?>(?!' . $begin . '(?>' . $tagName . '\b[^' . $end . ']*|\/' . $tagName . ')' . $end . ')' . $begin . '[^' . $begin . ']*)*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } else { + $regex = '(' . $begin . $tagName . '\b(?>(?:(?!' . $end . ').)*)' . $end . ')'; + $regex .= '(?:(?>(?:(?!' . $begin . ').)*)(?>(?!' . $begin . '(?>' . $tagName . '\b(?>(?:(?!' . $end . ').)*)|\/' . $tagName . ')' . $end . ')' . $begin . '(?>(?:(?!' . $begin . ').)*))*)'; + $regex .= '(' . $begin . '\/' . $tagName . $end . ')'; + } + break; + case 'restoreliteral': + $regex = ''; + break; + case 'include': + $name = 'file'; + case 'taglib': + case 'layout': + case 'extend': + if (empty($name)) { + $name = 'name'; + } + if ($single) { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>[^' . $end . ']*)' . $end; + } else { + $regex = $begin . $tagName . '\b\s+(?>(?:(?!' . $name . '=).)*)\b' . $name . '=([\'\"])(?P[\$\w\-\/\.\:@,\\\\]+)\\1(?>(?:(?!' . $end . ').)*)' . $end; + } + break; + } + } + + return '/' . $regex . '/is'; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['storege']); + + return $data; + } +} diff --git a/thinkphp/library/think/Url.php b/thinkphp/library/think/Url.php new file mode 100644 index 0000000..a339f8d --- /dev/null +++ b/thinkphp/library/think/Url.php @@ -0,0 +1,404 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class Url +{ + /** + * 配置参数 + * @var array + */ + protected $config = []; + + /** + * ROOT地址 + * @var string + */ + protected $root; + + /** + * 绑定检查 + * @var bool + */ + protected $bindCheck; + + /** + * 应用对象 + * @var App + */ + protected $app; + + public function __construct(App $app, array $config = []) + { + $this->app = $app; + $this->config = $config; + + if (is_file($app->getRuntimePath() . 'route.php')) { + // 读取路由映射文件 + $app['route']->setName(include $app->getRuntimePath() . 'route.php'); + } + } + + /** + * 初始化 + * @access public + * @param array $config + * @return void + */ + public function init(array $config = []) + { + $this->config = array_merge($this->config, array_change_key_case($config)); + } + + public static function __make(App $app, Config $config) + { + return new static($app, $config->pull('app')); + } + + /** + * URL生成 支持路由反射 + * @access public + * @param string $url 路由地址 + * @param string|array $vars 参数(支持数组和字符串)a=val&b=val2... ['a'=>'val1', 'b'=>'val2'] + * @param string|bool $suffix 伪静态后缀,默认为true表示获取配置值 + * @param boolean|string $domain 是否显示域名 或者直接传入域名 + * @return string + */ + public function build($url = '', $vars = '', $suffix = true, $domain = false) + { + // 解析URL + if (0 === strpos($url, '[') && $pos = strpos($url, ']')) { + // [name] 表示使用路由命名标识生成URL + $name = substr($url, 1, $pos - 1); + $url = 'name' . substr($url, $pos + 1); + } + + if (false === strpos($url, '://') && 0 !== strpos($url, '/')) { + $info = parse_url($url); + $url = !empty($info['path']) ? $info['path'] : ''; + + if (isset($info['fragment'])) { + // 解析锚点 + $anchor = $info['fragment']; + + if (false !== strpos($anchor, '?')) { + // 解析参数 + list($anchor, $info['query']) = explode('?', $anchor, 2); + } + + if (false !== strpos($anchor, '@')) { + // 解析域名 + list($anchor, $domain) = explode('@', $anchor, 2); + } + } elseif (strpos($url, '@') && false === strpos($url, '\\')) { + // 解析域名 + list($url, $domain) = explode('@', $url, 2); + } + } + + // 解析参数 + if (is_string($vars)) { + // aaa=1&bbb=2 转换成数组 + parse_str($vars, $vars); + } + + if ($url) { + $checkName = isset($name) ? $name : $url . (isset($info['query']) ? '?' . $info['query'] : ''); + $checkDomain = $domain && is_string($domain) ? $domain : null; + + $rule = $this->app['route']->getName($checkName, $checkDomain); + + if (is_null($rule) && isset($info['query'])) { + $rule = $this->app['route']->getName($url); + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + unset($info['query']); + } + } + + if (!empty($rule) && $match = $this->getRuleUrl($rule, $vars, $domain)) { + // 匹配路由命名标识 + $url = $match[0]; + + $domain = $match[1]; + + if (!is_null($match[2])) { + $suffix = $match[2]; + } + } elseif (!empty($rule) && isset($name)) { + throw new \InvalidArgumentException('route name not exists:' . $name); + } else { + // 检查别名路由 + $alias = $this->app['route']->getAlias(); + $matchAlias = false; + + if ($alias) { + // 别名路由解析 + foreach ($alias as $key => $item) { + $val = $item->getRoute(); + + if (0 === strpos($url, $val)) { + $url = $key . substr($url, strlen($val)); + $matchAlias = true; + break; + } + } + } + + if (!$matchAlias) { + // 路由标识不存在 直接解析 + $url = $this->parseUrl($url); + } + + // 检测URL绑定 + if (!$this->bindCheck) { + $bind = $this->app['route']->getBind($domain && is_string($domain) ? $domain : null); + + if ($bind && 0 === strpos($url, $bind)) { + $url = substr($url, strlen($bind) + 1); + } else { + $binds = $this->app['route']->getBind(true); + + foreach ($binds as $key => $val) { + if (is_string($val) && 0 === strpos($url, $val) && substr_count($val, '/') > 1) { + $url = substr($url, strlen($val) + 1); + $domain = $key; + break; + } + } + } + } + + if (isset($info['query'])) { + // 解析地址里面参数 合并到vars + parse_str($info['query'], $params); + $vars = array_merge($params, $vars); + } + } + + // 还原URL分隔符 + $depr = $this->config['pathinfo_depr']; + $url = str_replace('/', $depr, $url); + + // URL后缀 + if ('/' == substr($url, -1) || '' == $url) { + $suffix = ''; + } else { + $suffix = $this->parseSuffix($suffix); + } + + // 锚点 + $anchor = !empty($anchor) ? '#' . $anchor : ''; + + // 参数组装 + if (!empty($vars)) { + // 添加参数 + if ($this->config['url_common_param']) { + $vars = http_build_query($vars); + $url .= $suffix . '?' . $vars . $anchor; + } else { + $paramType = $this->config['url_param_type']; + + foreach ($vars as $var => $val) { + if ('' !== trim($val)) { + if ($paramType) { + $url .= $depr . urlencode($val); + } else { + $url .= $depr . $var . $depr . urlencode($val); + } + } + } + + $url .= $suffix . $anchor; + } + } else { + $url .= $suffix . $anchor; + } + + // 检测域名 + $domain = $this->parseDomain($url, $domain); + + // URL组装 + $url = $domain . rtrim($this->root ?: $this->app['request']->root(), '/') . '/' . ltrim($url, '/'); + + $this->bindCheck = false; + + return $url; + } + + // 直接解析URL地址 + protected function parseUrl($url) + { + $request = $this->app['request']; + + if (0 === strpos($url, '/')) { + // 直接作为路由地址解析 + $url = substr($url, 1); + } elseif (false !== strpos($url, '\\')) { + // 解析到类 + $url = ltrim(str_replace('\\', '/', $url), '/'); + } elseif (0 === strpos($url, '@')) { + // 解析到控制器 + $url = substr($url, 1); + } else { + // 解析到 模块/控制器/操作 + $module = $request->module(); + $module = $module ? $module . '/' : ''; + $controller = $request->controller(); + + if ('' == $url) { + $action = $request->action(); + } else { + $path = explode('/', $url); + $action = array_pop($path); + $controller = empty($path) ? $controller : array_pop($path); + $module = empty($path) ? $module : array_pop($path) . '/'; + } + + if ($this->config['url_convert']) { + $action = strtolower($action); + $controller = Loader::parseName($controller); + } + + $url = $module . $controller . '/' . $action; + } + + return $url; + } + + // 检测域名 + protected function parseDomain(&$url, $domain) + { + if (!$domain) { + return ''; + } + + $rootDomain = $this->app['request']->rootDomain(); + if (true === $domain) { + // 自动判断域名 + $domain = $this->config['app_host'] ?: $this->app['request']->host(); + + $domains = $this->app['route']->getDomains(); + + if ($domains) { + $route_domain = array_keys($domains); + foreach ($route_domain as $domain_prefix) { + if (0 === strpos($domain_prefix, '*.') && strpos($domain, ltrim($domain_prefix, '*.')) !== false) { + foreach ($domains as $key => $rule) { + $rule = is_array($rule) ? $rule[0] : $rule; + if (is_string($rule) && false === strpos($key, '*') && 0 === strpos($url, $rule)) { + $url = ltrim($url, $rule); + $domain = $key; + + // 生成对应子域名 + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + break; + } elseif (false !== strpos($key, '*')) { + if (!empty($rootDomain)) { + $domain .= $rootDomain; + } + + break; + } + } + } + } + } + } elseif (0 !== strpos($domain, $rootDomain) && false === strpos($domain, '.')) { + $domain .= '.' . $rootDomain; + } + + if (false !== strpos($domain, '://')) { + $scheme = ''; + } else { + $scheme = $this->app['request']->isSsl() || $this->config['is_https'] ? 'https://' : 'http://'; + + } + + return $scheme . $domain; + } + + // 解析URL后缀 + protected function parseSuffix($suffix) + { + if ($suffix) { + $suffix = true === $suffix ? $this->config['url_html_suffix'] : $suffix; + + if ($pos = strpos($suffix, '|')) { + $suffix = substr($suffix, 0, $pos); + } + } + + return (empty($suffix) || 0 === strpos($suffix, '.')) ? $suffix : '.' . $suffix; + } + + // 匹配路由地址 + public function getRuleUrl($rule, &$vars = [], $allowDomain = '') + { + foreach ($rule as $item) { + list($url, $pattern, $domain, $suffix, $method) = $item; + + if (is_string($allowDomain) && $domain != $allowDomain) { + continue; + } + + if (!in_array($this->app['request']->port(), [80, 443])) { + $domain .= ':' . $this->app['request']->port(); + } + + if (empty($pattern)) { + return [rtrim($url, '?/-'), $domain, $suffix]; + } + + $type = $this->config['url_common_param']; + + foreach ($pattern as $key => $val) { + if (isset($vars[$key])) { + $url = str_replace(['[:' . $key . ']', '<' . $key . '?>', ':' . $key, '<' . $key . '>'], $type ? $vars[$key] : urlencode($vars[$key]), $url); + unset($vars[$key]); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } elseif (2 == $val) { + $url = str_replace(['/[:' . $key . ']', '[:' . $key . ']', '<' . $key . '?>'], '', $url); + $url = str_replace(['/?', '-?'], ['/', '-'], $url); + $result = [rtrim($url, '?/-'), $domain, $suffix]; + } else { + break; + } + } + + if (isset($result)) { + return $result; + } + } + + return false; + } + + // 指定当前生成URL地址的root + public function root($root) + { + $this->root = $root; + $this->app['request']->setRoot($root); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app']); + + return $data; + } +} diff --git a/thinkphp/library/think/Validate.php b/thinkphp/library/think/Validate.php new file mode 100644 index 0000000..4672a5c --- /dev/null +++ b/thinkphp/library/think/Validate.php @@ -0,0 +1,1532 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +use think\exception\ClassNotFoundException; +use think\validate\ValidateRule; + +class Validate +{ + + /** + * 自定义验证类型 + * @var array + */ + protected static $type = []; + + /** + * 验证类型别名 + * @var array + */ + protected $alias = [ + '>' => 'gt', '>=' => 'egt', '<' => 'lt', '<=' => 'elt', '=' => 'eq', 'same' => 'eq', + ]; + + /** + * 当前验证规则 + * @var array + */ + protected $rule = []; + + /** + * 验证提示信息 + * @var array + */ + protected $message = []; + + /** + * 验证字段描述 + * @var array + */ + protected $field = []; + + /** + * 默认规则提示 + * @var array + */ + protected static $typeMsg = [ + 'require' => ':attribute require', + 'must' => ':attribute must', + 'number' => ':attribute must be numeric', + 'integer' => ':attribute must be integer', + 'float' => ':attribute must be float', + 'boolean' => ':attribute must be bool', + 'email' => ':attribute not a valid email address', + 'mobile' => ':attribute not a valid mobile', + 'array' => ':attribute must be a array', + 'accepted' => ':attribute must be yes,on or 1', + 'date' => ':attribute not a valid datetime', + 'file' => ':attribute not a valid file', + 'image' => ':attribute not a valid image', + 'alpha' => ':attribute must be alpha', + 'alphaNum' => ':attribute must be alpha-numeric', + 'alphaDash' => ':attribute must be alpha-numeric, dash, underscore', + 'activeUrl' => ':attribute not a valid domain or ip', + 'chs' => ':attribute must be chinese', + 'chsAlpha' => ':attribute must be chinese or alpha', + 'chsAlphaNum' => ':attribute must be chinese,alpha-numeric', + 'chsDash' => ':attribute must be chinese,alpha-numeric,underscore, dash', + 'url' => ':attribute not a valid url', + 'ip' => ':attribute not a valid ip', + 'dateFormat' => ':attribute must be dateFormat of :rule', + 'in' => ':attribute must be in :rule', + 'notIn' => ':attribute be notin :rule', + 'between' => ':attribute must between :1 - :2', + 'notBetween' => ':attribute not between :1 - :2', + 'length' => 'size of :attribute must be :rule', + 'max' => 'max size of :attribute must be :rule', + 'min' => 'min size of :attribute must be :rule', + 'after' => ':attribute cannot be less than :rule', + 'before' => ':attribute cannot exceed :rule', + 'afterWith' => ':attribute cannot be less than :rule', + 'beforeWith' => ':attribute cannot exceed :rule', + 'expire' => ':attribute not within :rule', + 'allowIp' => 'access IP is not allowed', + 'denyIp' => 'access IP denied', + 'confirm' => ':attribute out of accord with :2', + 'different' => ':attribute cannot be same with :2', + 'egt' => ':attribute must greater than or equal :rule', + 'gt' => ':attribute must greater than :rule', + 'elt' => ':attribute must less than or equal :rule', + 'lt' => ':attribute must less than :rule', + 'eq' => ':attribute must equal :rule', + 'unique' => ':attribute has exists', + 'regex' => ':attribute not conform to the rules', + 'method' => 'invalid Request method', + 'token' => 'invalid token', + 'fileSize' => 'filesize not match', + 'fileExt' => 'extensions to upload is not allowed', + 'fileMime' => 'mimetype to upload is not allowed', + ]; + + /** + * 当前验证场景 + * @var array + */ + protected $currentScene = null; + + /** + * Filter_var 规则 + * @var array + */ + protected $filter = [ + 'email' => FILTER_VALIDATE_EMAIL, + 'ip' => [FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6], + 'integer' => FILTER_VALIDATE_INT, + 'url' => FILTER_VALIDATE_URL, + 'macAddr' => FILTER_VALIDATE_MAC, + 'float' => FILTER_VALIDATE_FLOAT, + ]; + + /** + * 内置正则验证规则 + * @var array + */ + protected $regex = [ + 'alphaDash' => '/^[A-Za-z0-9\-\_]+$/', + 'chs' => '/^[\x{4e00}-\x{9fa5}]+$/u', + 'chsAlpha' => '/^[\x{4e00}-\x{9fa5}a-zA-Z]+$/u', + 'chsAlphaNum' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9]+$/u', + 'chsDash' => '/^[\x{4e00}-\x{9fa5}a-zA-Z0-9\_\-]+$/u', + 'mobile' => '/^1[3-9][0-9]\d{8}$/', + 'idCard' => '/(^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$)|(^[1-9]\d{5}\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{2}$)/', + 'zip' => '/\d{6}/', + ]; + + /** + * 验证场景定义 + * @var array + */ + protected $scene = []; + + /** + * 验证失败错误信息 + * @var array + */ + protected $error = []; + + /** + * 是否批量验证 + * @var bool + */ + protected $batch = false; + + /** + * 场景需要验证的规则 + * @var array + */ + protected $only = []; + + /** + * 场景需要移除的验证规则 + * @var array + */ + protected $remove = []; + + /** + * 场景需要追加的验证规则 + * @var array + */ + protected $append = []; + + /** + * 架构函数 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + */ + public function __construct(array $rules = [], array $message = [], array $field = []) + { + $this->rule = $rules + $this->rule; + $this->message = array_merge($this->message, $message); + $this->field = array_merge($this->field, $field); + } + + /** + * 创建一个验证器类 + * @access public + * @param array $rules 验证规则 + * @param array $message 验证提示信息 + * @param array $field 验证字段描述信息 + * @return Validate + */ + public static function make(array $rules = [], array $message = [], array $field = []) + { + return new self($rules, $message, $field); + } + + /** + * 添加字段验证规则 + * @access protected + * @param string|array $name 字段名称或者规则数组 + * @param mixed $rule 验证规则或者字段描述信息 + * @return $this + */ + public function rule($name, $rule = '') + { + if (is_array($name)) { + $this->rule = $name + $this->rule; + if (is_array($rule)) { + $this->field = array_merge($this->field, $rule); + } + } else { + $this->rule[$name] = $rule; + } + + return $this; + } + + /** + * 注册扩展验证(类型)规则 + * @access public + * @param string $type 验证规则类型 + * @param mixed $callback callback方法(或闭包) + * @return void + */ + public static function extend($type, $callback = null) + { + if (is_array($type)) { + self::$type = array_merge(self::$type, $type); + } else { + self::$type[$type] = $callback; + } + } + + /** + * 设置验证规则的默认提示信息 + * @access public + * @param string|array $type 验证规则类型名称或者数组 + * @param string $msg 验证提示信息 + * @return void + */ + public static function setTypeMsg($type, $msg = null) + { + if (is_array($type)) { + self::$typeMsg = array_merge(self::$typeMsg, $type); + } else { + self::$typeMsg[$type] = $msg; + } + } + + /** + * 设置提示信息 + * @access public + * @param string|array $name 字段名称 + * @param string $message 提示信息 + * @return Validate + */ + public function message($name, $message = '') + { + if (is_array($name)) { + $this->message = array_merge($this->message, $name); + } else { + $this->message[$name] = $message; + } + + return $this; + } + + /** + * 设置验证场景 + * @access public + * @param string $name 场景名 + * @return $this + */ + public function scene($name) + { + // 设置当前场景 + $this->currentScene = $name; + + return $this; + } + + /** + * 判断是否存在某个验证场景 + * @access public + * @param string $name 场景名 + * @return bool + */ + public function hasScene($name) + { + return isset($this->scene[$name]) || method_exists($this, 'scene' . $name); + } + + /** + * 设置批量验证 + * @access public + * @param bool $batch 是否批量验证 + * @return $this + */ + public function batch($batch = true) + { + $this->batch = $batch; + + return $this; + } + + /** + * 指定需要验证的字段列表 + * @access public + * @param array $fields 字段名 + * @return $this + */ + public function only($fields) + { + $this->only = $fields; + + return $this; + } + + /** + * 移除某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 null 移除所有规则 + * @return $this + */ + public function remove($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + if (is_int($key)) { + $this->remove($rule); + } else { + $this->remove($key, $rule); + } + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->remove[$field] = $rule; + } + + return $this; + } + + /** + * 追加某个字段的验证规则 + * @access public + * @param string|array $field 字段名 + * @param mixed $rule 验证规则 + * @return $this + */ + public function append($field, $rule = null) + { + if (is_array($field)) { + foreach ($field as $key => $rule) { + $this->append($key, $rule); + } + } else { + if (is_string($rule)) { + $rule = explode('|', $rule); + } + + $this->append[$field] = $rule; + } + + return $this; + } + + /** + * 数据自动验证 + * @access public + * @param array $data 数据 + * @param mixed $rules 验证规则 + * @param string $scene 验证场景 + * @return bool + */ + public function check($data, $rules = [], $scene = '') + { + $this->error = []; + + if (empty($rules)) { + // 读取验证规则 + $rules = $this->rule; + } + + // 获取场景定义 + $this->getScene($scene); + + foreach ($this->append as $key => $rule) { + if (!isset($rules[$key])) { + $rules[$key] = $rule; + } + } + + foreach ($rules as $key => $rule) { + // field => 'rule1|rule2...' field => ['rule1','rule2',...] + if (strpos($key, '|')) { + // 字段|描述 用于指定属性名称 + list($key, $title) = explode('|', $key); + } else { + $title = isset($this->field[$key]) ? $this->field[$key] : $key; + } + + // 场景检测 + if (!empty($this->only) && !in_array($key, $this->only)) { + continue; + } + + // 获取数据 支持多维数组 + $value = $this->getDataValue($data, $key); + + // 字段验证 + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data, $title, $this]); + } elseif ($rule instanceof ValidateRule) { + // 验证因子 + $result = $this->checkItem($key, $value, $rule->getRule(), $data, $rule->getTitle() ?: $title, $rule->getMsg()); + } else { + $result = $this->checkItem($key, $value, $rule, $data, $title); + } + + if (true !== $result) { + // 没有返回true 则表示验证失败 + if (!empty($this->batch)) { + // 批量验证 + if (is_array($result)) { + $this->error = array_merge($this->error, $result); + } else { + $this->error[$key] = $result; + } + } else { + $this->error = $result; + return false; + } + } + } + + return !empty($this->error) ? false : true; + } + + /** + * 根据验证规则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @return bool + */ + public function checkRule($value, $rules) + { + if ($rules instanceof \Closure) { + return call_user_func_array($rules, [$value]); + } elseif ($rules instanceof ValidateRule) { + $rules = $rules->getRule(); + } elseif (is_string($rules)) { + $rules = explode('|', $rules); + } + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value]); + } else { + // 判断验证类型 + list($type, $rule) = $this->getValidateType($key, $rule); + + $callback = isset(self::$type[$type]) ? self::$type[$type] : [$this, $type]; + + $result = call_user_func_array($callback, [$value, $rule]); + } + + if (true !== $result) { + return $result; + } + } + + return true; + } + + /** + * 验证单个字段规则 + * @access protected + * @param string $field 字段名 + * @param mixed $value 字段值 + * @param mixed $rules 验证规则 + * @param array $data 数据 + * @param string $title 字段描述 + * @param array $msg 提示信息 + * @return mixed + */ + protected function checkItem($field, $value, $rules, $data, $title = '', $msg = []) + { + if (isset($this->remove[$field]) && true === $this->remove[$field] && empty($this->append[$field])) { + // 字段已经移除 无需验证 + return true; + } + + // 支持多规则验证 require|in:a,b,c|... 或者 ['require','in'=>'a,b,c',...] + if (is_string($rules)) { + $rules = explode('|', $rules); + } + + if (isset($this->append[$field])) { + // 追加额外的验证规则 + $rules = array_merge($rules, $this->append[$field]); + } + + $i = 0; + $result = true; + + foreach ($rules as $key => $rule) { + if ($rule instanceof \Closure) { + $result = call_user_func_array($rule, [$value, $data]); + $info = is_numeric($key) ? '' : $key; + } else { + // 判断验证类型 + list($type, $rule, $info) = $this->getValidateType($key, $rule); + + if (isset($this->append[$field]) && in_array($info, $this->append[$field])) { + + } elseif (array_key_exists($field, $this->remove) && (null === $this->remove[$field] || in_array($info, $this->remove[$field]))) { + // 规则已经移除 + $i++; + continue; + } + + // 验证类型 + if (isset(self::$type[$type])) { + $result = call_user_func_array(self::$type[$type], [$value, $rule, $data, $field, $title]); + } elseif ('must' == $info || 0 === strpos($info, 'require') || (!is_null($value) && '' !== $value)) { + // 验证数据 + $result = call_user_func_array([$this, $type], [$value, $rule, $data, $field, $title]); + } else { + $result = true; + } + } + + if (false === $result) { + // 验证失败 返回错误信息 + if (!empty($msg[$i])) { + $message = $msg[$i]; + if (is_string($message) && strpos($message, '{%') === 0) { + $message = facade\Lang::get(substr($message, 2, -1)); + } + } else { + $message = $this->getRuleMsg($field, $title, $info, $rule); + } + + return $message; + } elseif (true !== $result) { + // 返回自定义错误信息 + if (is_string($result) && false !== strpos($result, ':')) { + $result = str_replace( + [':attribute', ':rule'], + [$title, (string) $rule], + $result); + } + + return $result; + } + $i++; + } + + return $result; + } + + /** + * 获取当前验证类型及规则 + * @access public + * @param mixed $key + * @param mixed $rule + * @return array + */ + protected function getValidateType($key, $rule) + { + // 判断验证类型 + if (!is_numeric($key)) { + return [$key, $rule, $key]; + } + + if (strpos($rule, ':')) { + list($type, $rule) = explode(':', $rule, 2); + if (isset($this->alias[$type])) { + // 判断别名 + $type = $this->alias[$type]; + } + $info = $type; + } elseif (method_exists($this, $rule)) { + $type = $rule; + $info = $rule; + $rule = ''; + } else { + $type = 'is'; + $info = $rule; + } + + return [$type, $rule, $info]; + } + + /** + * 验证是否和某个字段的值一致 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @param string $field 字段名 + * @return bool + */ + public function confirm($value, $rule, $data = [], $field = '') + { + if ('' == $rule) { + if (strpos($field, '_confirm')) { + $rule = strstr($field, '_confirm', true); + } else { + $rule = $field . '_confirm'; + } + } + + return $this->getDataValue($data, $rule) === $value; + } + + /** + * 验证是否和某个字段的值是否不同 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function different($value, $rule, $data = []) + { + return $this->getDataValue($data, $rule) != $value; + } + + /** + * 验证是否大于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function egt($value, $rule, $data = []) + { + return $value >= $this->getDataValue($data, $rule); + } + + /** + * 验证是否大于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function gt($value, $rule, $data) + { + return $value > $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function elt($value, $rule, $data = []) + { + return $value <= $this->getDataValue($data, $rule); + } + + /** + * 验证是否小于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function lt($value, $rule, $data = []) + { + return $value < $this->getDataValue($data, $rule); + } + + /** + * 验证是否等于某个值 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function eq($value, $rule) + { + return $value == $rule; + } + + /** + * 必须验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function must($value, $rule = null) + { + return !empty($value) || '0' == $value; + } + + /** + * 验证字段值是否为有效格式 + * @access public + * @param mixed $value 字段值 + * @param string $rule 验证规则 + * @param array $data 验证数据 + * @return bool + */ + public function is($value, $rule, $data = []) + { + switch (Loader::parseName($rule, 1, false)) { + case 'require': + // 必须 + $result = !empty($value) || '0' == $value; + break; + case 'accepted': + // 接受 + $result = in_array($value, ['1', 'on', 'yes']); + break; + case 'date': + // 是否是一个有效日期 + $result = false !== strtotime($value); + break; + case 'activeUrl': + // 是否为有效的网址 + $result = checkdnsrr($value); + break; + case 'boolean': + case 'bool': + // 是否为布尔值 + $result = in_array($value, [true, false, 0, 1, '0', '1'], true); + break; + case 'number': + $result = ctype_digit((string) $value); + break; + case 'alphaNum': + $result = ctype_alnum($value); + break; + case 'array': + // 是否为数组 + $result = is_array($value); + break; + case 'file': + $result = $value instanceof File; + break; + case 'image': + $result = $value instanceof File && in_array($this->getImageType($value->getRealPath()), [1, 2, 3, 6]); + break; + case 'token': + $result = $this->token($value, '__token__', $data); + break; + default: + if (isset(self::$type[$rule])) { + // 注册的验证规则 + $result = call_user_func_array(self::$type[$rule], [$value]); + } elseif (function_exists('ctype_' . $rule)) { + // ctype验证规则 + $ctypeFun = 'ctype_' . $rule; + $result = $ctypeFun($value); + } elseif (isset($this->filter[$rule])) { + // Filter_var验证规则 + $result = $this->filter($value, $this->filter[$rule]); + } else { + // 正则验证 + $result = $this->regex($value, $rule); + } + } + + return $result; + } + + // 判断图像类型 + protected function getImageType($image) + { + if (function_exists('exif_imagetype')) { + return exif_imagetype($image); + } + + try { + $info = getimagesize($image); + return $info ? $info[2] : false; + } catch (\Exception $e) { + return false; + } + } + + /** + * 验证是否为合格的域名或者IP 支持A,MX,NS,SOA,PTR,CNAME,AAAA,A6, SRV,NAPTR,TXT 或者 ANY类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function activeUrl($value, $rule = 'MX') + { + if (!in_array($rule, ['A', 'MX', 'NS', 'SOA', 'PTR', 'CNAME', 'AAAA', 'A6', 'SRV', 'NAPTR', 'TXT', 'ANY'])) { + $rule = 'MX'; + } + + return checkdnsrr($value, $rule); + } + + /** + * 验证是否有效IP + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 ipv4 ipv6 + * @return bool + */ + public function ip($value, $rule = 'ipv4') + { + if (!in_array($rule, ['ipv4', 'ipv6'])) { + $rule = 'ipv4'; + } + + return $this->filter($value, [FILTER_VALIDATE_IP, 'ipv6' == $rule ? FILTER_FLAG_IPV6 : FILTER_FLAG_IPV4]); + } + + /** + * 验证上传文件后缀 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileExt($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkExt($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkExt($rule); + } + + return false; + } + + /** + * 验证上传文件类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileMime($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkMime($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkMime($rule); + } + + return false; + } + + /** + * 验证上传文件大小 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function fileSize($file, $rule) + { + if (is_array($file)) { + foreach ($file as $item) { + if (!($item instanceof File) || !$item->checkSize($rule)) { + return false; + } + } + return true; + } elseif ($file instanceof File) { + return $file->checkSize($rule); + } + + return false; + } + + /** + * 验证图片的宽高及类型 + * @access public + * @param mixed $file 上传文件 + * @param mixed $rule 验证规则 + * @return bool + */ + public function image($file, $rule) + { + if (!($file instanceof File)) { + return false; + } + + if ($rule) { + $rule = explode(',', $rule); + + list($width, $height, $type) = getimagesize($file->getRealPath()); + + if (isset($rule[2])) { + $imageType = strtolower($rule[2]); + + if ('jpeg' == $imageType) { + $imageType = 'jpg'; + } + + if (image_type_to_extension($type, false) != $imageType) { + return false; + } + } + + list($w, $h) = $rule; + + return $w == $width && $h == $height; + } + + return in_array($this->getImageType($file->getRealPath()), [1, 2, 3, 6]); + } + + /** + * 验证请求类型 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function method($value, $rule) + { + $method = Container::get('request')->method(); + return strtoupper($rule) == $method; + } + + /** + * 验证时间和日期是否符合指定格式 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function dateFormat($value, $rule) + { + $info = date_parse_from_format($rule, $value); + return 0 == $info['warning_count'] && 0 == $info['error_count']; + } + + /** + * 验证是否唯一 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 格式:数据表,字段名,排除ID,主键名 + * @param array $data 数据 + * @param string $field 验证字段名 + * @return bool + */ + public function unique($value, $rule, $data, $field) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + if (false !== strpos($rule[0], '\\')) { + // 指定模型类 + $db = new $rule[0]; + } else { + try { + $db = Container::get('app')->model($rule[0]); + } catch (ClassNotFoundException $e) { + $db = Db::name($rule[0]); + } + } + + $key = isset($rule[1]) ? $rule[1] : $field; + + if (strpos($key, '^')) { + // 支持多个字段验证 + $fields = explode('^', $key); + foreach ($fields as $key) { + $map[] = [$key, '=', $data[$key]]; + } + } else { + $map[] = [$key, '=', $data[$field]]; + } + + $pk = !empty($rule[3]) ? $rule[3] : $db->getPk(); + + if (is_string($pk)) { + if (isset($rule[2])) { + $map[] = [$pk, '<>', $rule[2]]; + } elseif (isset($data[$pk])) { + $map[] = [$pk, '<>', $data[$pk]]; + } + } + + if ($db->where($map)->field($pk)->find()) { + return false; + } + + return true; + } + + /** + * 使用行为类验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return mixed + */ + public function behavior($value, $rule, $data) + { + return Container::get('hook')->exec($rule, $data); + } + + /** + * 使用filter_var方式验证 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function filter($value, $rule) + { + if (is_string($rule) && strpos($rule, ',')) { + list($rule, $param) = explode(',', $rule); + } elseif (is_array($rule)) { + $param = isset($rule[1]) ? $rule[1] : null; + $rule = $rule[0]; + } else { + $param = null; + } + + return false !== filter_var($value, is_int($rule) ? $rule : filter_id($rule), $param); + } + + /** + * 验证某个字段等于某个值的时候必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireIf($value, $rule, $data) + { + list($field, $val) = explode(',', $rule); + + if ($this->getDataValue($data, $field) == $val) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 通过回调方法验证某个字段是否必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireCallback($value, $rule, $data) + { + $result = call_user_func_array([$this, $rule], [$value, $data]); + + if ($result) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证某个字段有值的情况下必须 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function requireWith($value, $rule, $data) + { + $val = $this->getDataValue($data, $rule); + + if (!empty($val)) { + return !empty($value) || '0' == $value; + } + + return true; + } + + /** + * 验证是否在范围内 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function in($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证是否不在某个范围 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notIn($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * between验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function between($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + + return $value >= $min && $value <= $max; + } + + /** + * 使用notbetween验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function notBetween($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + list($min, $max) = $rule; + + return $value < $min || $value > $max; + } + + /** + * 验证数据长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function length($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + if (strpos($rule, ',')) { + // 长度区间 + list($min, $max) = explode(',', $rule); + return $length >= $min && $length <= $max; + } + + // 指定长度 + return $length == $rule; + } + + /** + * 验证数据最大长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function max($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length <= $rule; + } + + /** + * 验证数据最小长度 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function min($value, $rule) + { + if (is_array($value)) { + $length = count($value); + } elseif ($value instanceof File) { + $length = $value->getSize(); + } else { + $length = mb_strlen((string) $value); + } + + return $length >= $rule; + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function after($value, $rule, $data) + { + return strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function before($value, $rule, $data) + { + return strtotime($value) <= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function afterWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) >= strtotime($rule); + } + + /** + * 验证日期字段 + * @access protected + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + protected function beforeWith($value, $rule, $data) + { + $rule = $this->getDataValue($data, $rule); + return !is_null($rule) && strtotime($value) <= strtotime($rule); + } + + /** + * 验证有效期 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @return bool + */ + public function expire($value, $rule) + { + if (is_string($rule)) { + $rule = explode(',', $rule); + } + + list($start, $end) = $rule; + + if (!is_numeric($start)) { + $start = strtotime($start); + } + + if (!is_numeric($end)) { + $end = strtotime($end); + } + + return $_SERVER['REQUEST_TIME'] >= $start && $_SERVER['REQUEST_TIME'] <= $end; + } + + /** + * 验证IP许可 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + public function allowIp($value, $rule) + { + return in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 验证IP禁用 + * @access public + * @param string $value 字段值 + * @param mixed $rule 验证规则 + * @return mixed + */ + public function denyIp($value, $rule) + { + return !in_array($value, is_array($rule) ? $rule : explode(',', $rule)); + } + + /** + * 使用正则验证数据 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 正则规则或者预定义正则名 + * @return bool + */ + public function regex($value, $rule) + { + if (isset($this->regex[$rule])) { + $rule = $this->regex[$rule]; + } + + if (0 !== strpos($rule, '/') && !preg_match('/\/[imsU]{0,4}$/', $rule)) { + // 不是正则表达式则两端补上/ + $rule = '/^' . $rule . '$/'; + } + + return is_scalar($value) && 1 === preg_match($rule, (string) $value); + } + + /** + * 验证表单令牌 + * @access public + * @param mixed $value 字段值 + * @param mixed $rule 验证规则 + * @param array $data 数据 + * @return bool + */ + public function token($value, $rule, $data) + { + $rule = !empty($rule) ? $rule : '__token__'; + $session = Container::get('session'); + + if (!isset($data[$rule]) || !$session->has($rule)) { + // 令牌数据无效 + return false; + } + + // 令牌验证 + if (isset($data[$rule]) && $session->get($rule) === $data[$rule]) { + // 防止重复提交 + $session->delete($rule); // 验证完成销毁session + return true; + } + + // 开启TOKEN重置 + $session->delete($rule); + + return false; + } + + // 获取错误信息 + public function getError() + { + return $this->error; + } + + /** + * 获取数据值 + * @access protected + * @param array $data 数据 + * @param string $key 数据标识 支持多维 + * @return mixed + */ + protected function getDataValue($data, $key) + { + if (is_numeric($key)) { + $value = $key; + } elseif (strpos($key, '.')) { + // 支持多维数组验证 + foreach (explode('.', $key) as $key) { + if (!isset($data[$key])) { + $value = null; + break; + } + $value = $data = $data[$key]; + } + } else { + $value = isset($data[$key]) ? $data[$key] : null; + } + + return $value; + } + + /** + * 获取验证规则的错误提示信息 + * @access protected + * @param string $attribute 字段英文名 + * @param string $title 字段描述名 + * @param string $type 验证规则名称 + * @param mixed $rule 验证规则数据 + * @return string + */ + protected function getRuleMsg($attribute, $title, $type, $rule) + { + $lang = Container::get('lang'); + + if (isset($this->message[$attribute . '.' . $type])) { + $msg = $this->message[$attribute . '.' . $type]; + } elseif (isset($this->message[$attribute][$type])) { + $msg = $this->message[$attribute][$type]; + } elseif (isset($this->message[$attribute])) { + $msg = $this->message[$attribute]; + } elseif (isset(self::$typeMsg[$type])) { + $msg = self::$typeMsg[$type]; + } elseif (0 === strpos($type, 'require')) { + $msg = self::$typeMsg['require']; + } else { + $msg = $title . $lang->get('not conform to the rules'); + } + + if (is_string($msg) && 0 === strpos($msg, '{%')) { + $msg = $lang->get(substr($msg, 2, -1)); + } elseif ($lang->has($msg)) { + $msg = $lang->get($msg); + } + + if (is_string($msg) && is_scalar($rule) && false !== strpos($msg, ':')) { + // 变量替换 + if (is_string($rule) && strpos($rule, ',')) { + $array = array_pad(explode(',', $rule), 3, ''); + } else { + $array = array_pad([], 3, ''); + } + $msg = str_replace( + [':attribute', ':rule', ':1', ':2', ':3'], + [$title, (string) $rule, $array[0], $array[1], $array[2]], + $msg); + } + + return $msg; + } + + /** + * 获取数据验证的场景 + * @access protected + * @param string $scene 验证场景 + * @return void + */ + protected function getScene($scene = '') + { + if (empty($scene)) { + // 读取指定场景 + $scene = $this->currentScene; + } + + if (empty($scene)) { + return; + } + + $this->only = $this->append = $this->remove = []; + + if (method_exists($this, 'scene' . $scene)) { + call_user_func([$this, 'scene' . $scene]); + } elseif (isset($this->scene[$scene])) { + // 如果设置了验证适用场景 + $scene = $this->scene[$scene]; + + if (is_string($scene)) { + $scene = explode(',', $scene); + } + + $this->only = $scene; + } + } + + /** + * 动态方法 直接调用is方法进行验证 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return bool + */ + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_push($args, lcfirst($method)); + + return call_user_func_array([$this, 'is'], $args); + } +} diff --git a/thinkphp/library/think/View.php b/thinkphp/library/think/View.php new file mode 100644 index 0000000..17860a6 --- /dev/null +++ b/thinkphp/library/think/View.php @@ -0,0 +1,250 @@ + +// +---------------------------------------------------------------------- + +namespace think; + +class View +{ + /** + * 模板引擎实例 + * @var object + */ + public $engine; + + /** + * 模板变量 + * @var array + */ + protected $data = []; + + /** + * 内容过滤 + * @var mixed + */ + protected $filter; + + /** + * 全局模板变量 + * @var array + */ + protected static $var = []; + + /** + * 初始化 + * @access public + * @param mixed $engine 模板引擎参数 + * @return $this + */ + public function init($engine = []) + { + // 初始化模板引擎 + $this->engine($engine); + + return $this; + } + + public static function __make(Config $config) + { + return (new static())->init($config->pull('template')); + } + + /** + * 模板变量静态赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function share($name, $value = '') + { + if (is_array($name)) { + self::$var = array_merge(self::$var, $name); + } else { + self::$var[$name] = $value; + } + + return $this; + } + + /** + * 清理模板变量 + * @access public + * @return void + */ + public function clear() + { + self::$var = []; + $this->data = []; + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->data = array_merge($this->data, $name); + } else { + $this->data[$name] = $value; + } + + return $this; + } + + /** + * 设置当前模板解析的引擎 + * @access public + * @param array|string $options 引擎参数 + * @return $this + */ + public function engine($options = []) + { + if (is_string($options)) { + $type = $options; + $options = []; + } else { + $type = !empty($options['type']) ? $options['type'] : 'Think'; + } + + if (isset($options['type'])) { + unset($options['type']); + } + + $this->engine = Loader::factory($type, '\\think\\view\\driver\\', $options); + + return $this; + } + + /** + * 配置模板引擎 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function config($name, $value = null) + { + $this->engine->config($name, $value); + + return $this; + } + + /** + * 检查模板是否存在 + * @access public + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->engine->exists($name); + } + + /** + * 视图过滤 + * @access public + * @param Callable $filter 过滤方法或闭包 + * @return $this + */ + public function filter($filter) + { + $this->filter = $filter; + return $this; + } + + /** + * 解析和获取模板内容 用于输出 + * @access public + * @param string $template 模板文件名或者内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @param bool $renderContent 是否渲染内容 + * @return string + * @throws \Exception + */ + public function fetch($template = '', $vars = [], $config = [], $renderContent = false) + { + // 模板变量 + $vars = array_merge(self::$var, $this->data, $vars); + + // 页面缓存 + ob_start(); + ob_implicit_flush(0); + + // 渲染输出 + try { + $method = $renderContent ? 'display' : 'fetch'; + $this->engine->$method($template, $vars, $config); + } catch (\Exception $e) { + ob_end_clean(); + throw $e; + } + + // 获取并清空缓存 + $content = ob_get_clean(); + + if ($this->filter) { + $content = call_user_func_array($this->filter, [$content]); + } + + return $content; + } + + /** + * 渲染内容输出 + * @access public + * @param string $content 内容 + * @param array $vars 模板输出变量 + * @param array $config 模板参数 + * @return mixed + */ + public function display($content, $vars = [], $config = []) + { + return $this->fetch($content, $vars, $config, true); + } + + /** + * 模板变量赋值 + * @access public + * @param string $name 变量名 + * @param mixed $value 变量值 + */ + public function __set($name, $value) + { + $this->data[$name] = $value; + } + + /** + * 取得模板显示变量的值 + * @access protected + * @param string $name 模板变量 + * @return mixed + */ + public function __get($name) + { + return $this->data[$name]; + } + + /** + * 检测模板变量是否设置 + * @access public + * @param string $name 模板变量名 + * @return bool + */ + public function __isset($name) + { + return isset($this->data[$name]); + } +} diff --git a/thinkphp/library/think/cache/Driver.php b/thinkphp/library/think/cache/Driver.php new file mode 100644 index 0000000..f0ec7ba --- /dev/null +++ b/thinkphp/library/think/cache/Driver.php @@ -0,0 +1,353 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache; + +use think\Container; + +/** + * 缓存基础类 + */ +abstract class Driver +{ + /** + * 驱动句柄 + * @var object + */ + protected $handler = null; + + /** + * 缓存读取次数 + * @var integer + */ + protected $readTimes = 0; + + /** + * 缓存写入次数 + * @var integer + */ + protected $writeTimes = 0; + + /** + * 缓存参数 + * @var array + */ + protected $options = []; + + /** + * 缓存标签 + * @var string + */ + protected $tag; + + /** + * 序列化方法 + * @var array + */ + protected static $serialize = ['serialize', 'unserialize', 'think_serialize:', 16]; + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + abstract public function has($name); + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + abstract public function get($name, $default = false); + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return boolean + */ + abstract public function set($name, $value, $expire = null); + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function inc($name, $step = 1); + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + abstract public function dec($name, $step = 1); + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + abstract public function rm($name); + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + abstract public function clear($tag = null); + + /** + * 获取有效期 + * @access protected + * @param integer|\DateTime $expire 有效期 + * @return integer + */ + protected function getExpireTime($expire) + { + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp() - time(); + } + + return $expire; + } + + /** + * 获取实际的缓存标识 + * @access protected + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . $name; + } + + /** + * 读取缓存并删除 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function pull($name) + { + $result = $this->get($name, false); + + if ($result) { + $this->rm($name); + return $result; + } else { + return; + } + } + + /** + * 如果不存在则写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int $expire 有效时间 0为永久 + * @return mixed + */ + public function remember($name, $value, $expire = null) + { + if (!$this->has($name)) { + $time = time(); + while ($time + 5 > time() && $this->has($name . '_lock')) { + // 存在锁定则等待 + usleep(200000); + } + + try { + // 锁定 + $this->set($name . '_lock', true); + + if ($value instanceof \Closure) { + // 获取缓存数据 + $value = Container::getInstance()->invokeFunction($value); + } + + // 缓存数据 + $this->set($name, $value, $expire); + + // 解锁 + $this->rm($name . '_lock'); + } catch (\Exception $e) { + $this->rm($name . '_lock'); + throw $e; + } catch (\throwable $e) { + $this->rm($name . '_lock'); + throw $e; + } + } else { + $value = $this->get($name); + } + + return $value; + } + + /** + * 缓存标签 + * @access public + * @param string $name 标签名 + * @param string|array $keys 缓存标识 + * @param bool $overlay 是否覆盖 + * @return $this + */ + public function tag($name, $keys = null, $overlay = false) + { + if (is_null($name)) { + + } elseif (is_null($keys)) { + $this->tag = $name; + } else { + $key = 'tag_' . md5($name); + + if (is_string($keys)) { + $keys = explode(',', $keys); + } + + $keys = array_map([$this, 'getCacheKey'], $keys); + + if ($overlay) { + $value = $keys; + } else { + $value = array_unique(array_merge($this->getTagItem($name), $keys)); + } + + $this->set($key, implode(',', $value), 0); + } + + return $this; + } + + /** + * 更新标签 + * @access protected + * @param string $name 缓存标识 + * @return void + */ + protected function setTagItem($name) + { + if ($this->tag) { + $key = 'tag_' . md5($this->tag); + $prev = $this->tag; + $this->tag = null; + + if ($this->has($key)) { + $value = explode(',', $this->get($key)); + $value[] = $name; + $value = implode(',', array_unique($value)); + } else { + $value = $name; + } + + $this->set($key, $value, 0); + $this->tag = $prev; + } + } + + /** + * 获取标签包含的缓存标识 + * @access protected + * @param string $tag 缓存标签 + * @return array + */ + protected function getTagItem($tag) + { + $key = 'tag_' . md5($tag); + $value = $this->get($key); + + if ($value) { + return array_filter(explode(',', $value)); + } else { + return []; + } + } + + /** + * 序列化数据 + * @access protected + * @param mixed $data + * @return string + */ + protected function serialize($data) + { + if (is_scalar($data) || !$this->options['serialize']) { + return $data; + } + + $serialize = self::$serialize[0]; + + return self::$serialize[2] . $serialize($data); + } + + /** + * 反序列化数据 + * @access protected + * @param string $data + * @return mixed + */ + protected function unserialize($data) + { + if ($this->options['serialize'] && 0 === strpos($data, self::$serialize[2])) { + $unserialize = self::$serialize[1]; + + return $unserialize(substr($data, self::$serialize[3])); + } else { + return $data; + } + } + + /** + * 注册序列化机制 + * @access public + * @param callable $serialize 序列化方法 + * @param callable $unserialize 反序列化方法 + * @param string $prefix 序列化前缀标识 + * @return $this + */ + public static function registerSerialize($serialize, $unserialize, $prefix = 'think_serialize:') + { + self::$serialize = [$serialize, $unserialize, $prefix, strlen($prefix)]; + } + + /** + * 返回句柄对象,可执行其它高级方法 + * + * @access public + * @return object + */ + public function handler() + { + return $this->handler; + } + + public function getReadTimes() + { + return $this->readTimes; + } + + public function getWriteTimes() + { + return $this->writeTimes; + } +} diff --git a/thinkphp/library/think/cache/driver/File.php b/thinkphp/library/think/cache/driver/File.php new file mode 100644 index 0000000..7c5661e --- /dev/null +++ b/thinkphp/library/think/cache/driver/File.php @@ -0,0 +1,305 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; +use think\Container; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class File extends Driver +{ + protected $options = [ + 'expire' => 0, + 'cache_subdir' => true, + 'prefix' => '', + 'path' => '', + 'hash_type' => 'md5', + 'data_compress' => false, + 'serialize' => true, + ]; + + protected $expire; + + /** + * 架构函数 + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (empty($this->options['path'])) { + $this->options['path'] = Container::get('app')->getRuntimePath() . 'cache' . DIRECTORY_SEPARATOR; + } elseif (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + + $this->init(); + } + + /** + * 初始化检查 + * @access private + * @return boolean + */ + private function init() + { + // 创建项目缓存目录 + try { + if (!is_dir($this->options['path']) && mkdir($this->options['path'], 0755, true)) { + return true; + } + } catch (\Exception $e) { + } + + return false; + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @param bool $auto 是否自动创建目录 + * @return string + */ + protected function getCacheKey($name, $auto = false) + { + $name = hash($this->options['hash_type'], $name); + + if ($this->options['cache_subdir']) { + // 使用子目录 + $name = substr($name, 0, 2) . DIRECTORY_SEPARATOR . substr($name, 2); + } + + if ($this->options['prefix']) { + $name = $this->options['prefix'] . DIRECTORY_SEPARATOR . $name; + } + + $filename = $this->options['path'] . $name . '.php'; + $dir = dirname($filename); + + if ($auto && !is_dir($dir)) { + try { + mkdir($dir, 0755, true); + } catch (\Exception $e) { + } + } + + return $filename; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return false !== $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $filename = $this->getCacheKey($name); + + if (!is_file($filename)) { + return $default; + } + + $content = file_get_contents($filename); + $this->expire = null; + + if (false !== $content) { + $expire = (int) substr($content, 8, 12); + if (0 != $expire && time() > filemtime($filename) + $expire) { + //缓存过期删除缓存文件 + $this->unlink($filename); + return $default; + } + + $this->expire = $expire; + $content = substr($content, 32); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + return $this->unserialize($content); + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $expire = $this->getExpireTime($expire); + $filename = $this->getCacheKey($name, true); + + if ($this->tag && !is_file($filename)) { + $first = true; + } + + $data = $this->serialize($value); + + if ($this->options['data_compress'] && function_exists('gzcompress')) { + //数据压缩 + $data = gzcompress($data, 3); + } + + $data = "\n" . $data; + $result = file_put_contents($filename, $data); + + if ($result) { + isset($first) && $this->setTagItem($filename); + clearstatcache(); + return true; + } else { + return false; + } + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + $expire = $this->expire; + } else { + $value = $step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + $expire = $this->expire; + } else { + $value = -$step; + $expire = 0; + } + + return $this->set($name, $value, $expire) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + try { + return $this->unlink($this->getCacheKey($name)); + } catch (\Exception $e) { + } + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->unlink($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + + $this->writeTimes++; + + $files = (array) glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*'); + + foreach ($files as $path) { + if (is_dir($path)) { + $matches = glob($path . DIRECTORY_SEPARATOR . '*.php'); + if (is_array($matches)) { + array_map('unlink', $matches); + } + rmdir($path); + } else { + unlink($path); + } + } + + return true; + } + + /** + * 判断文件是否存在后,删除 + * @access private + * @param string $path + * @return bool + * @author byron sampson + * @return boolean + */ + private function unlink($path) + { + return is_file($path) && unlink($path); + } + +} diff --git a/thinkphp/library/think/cache/driver/Lite.php b/thinkphp/library/think/cache/driver/Lite.php new file mode 100644 index 0000000..544663c --- /dev/null +++ b/thinkphp/library/think/cache/driver/Lite.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * 文件类型缓存类 + * @author liu21st + */ +class Lite extends Driver +{ + protected $options = [ + 'prefix' => '', + 'path' => '', + 'expire' => 0, // 等于 10*365*24*3600(10年) + ]; + + /** + * 架构函数 + * @access public + * + * @param array $options + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (substr($this->options['path'], -1) != DIRECTORY_SEPARATOR) { + $this->options['path'] .= DIRECTORY_SEPARATOR; + } + + } + + /** + * 取得变量的存储文件名 + * @access protected + * @param string $name 缓存变量名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['path'] . $this->options['prefix'] . md5($name) . '.php'; + } + + /** + * 判断缓存是否存在 + * @access public + * @param string $name 缓存变量名 + * @return mixed + */ + public function has($name) + { + return $this->get($name) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $filename = $this->getCacheKey($name); + + if (is_file($filename)) { + // 判断是否过期 + $mtime = filemtime($filename); + + if ($mtime < time()) { + // 清除已经过期的文件 + unlink($filename); + return $default; + } + + return include $filename; + } else { + return $default; + } + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|\DateTime $expire 有效时间 0为永久 + * @return bool + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = 0 === $expire ? 10 * 365 * 24 * 3600 : $expire; + $expire = time() + $expire; + } + + $filename = $this->getCacheKey($name); + + if ($this->tag && !is_file($filename)) { + $first = true; + } + + $ret = file_put_contents($filename, ("setTagItem($filename); + touch($filename, $expire); + } + + return $ret; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return unlink($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + unlink($key); + } + + $this->rm('tag_' . md5($tag)); + return true; + } + + $this->writeTimes++; + + array_map("unlink", glob($this->options['path'] . ($this->options['prefix'] ? $this->options['prefix'] . DIRECTORY_SEPARATOR : '') . '*.php')); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcache.php b/thinkphp/library/think/cache/driver/Memcache.php new file mode 100644 index 0000000..162ca52 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Memcache.php @@ -0,0 +1,203 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcache extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'persistent' => true, + 'prefix' => '', + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!extension_loaded('memcache')) { + throw new \BadFunctionCallException('not support: memcache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcache; + + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->options['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->options['persistent'], 1, $this->options['timeout']) : + $this->handler->addServer($host, $port, $this->options['persistent'], 1); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + + return false !== $this->handler->get($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param int|DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, 0, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + $this->handler->delete($key); + } + + $this->rm('tag_' . md5($tag)); + return true; + } + + $this->writeTimes++; + + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Memcached.php b/thinkphp/library/think/cache/driver/Memcached.php new file mode 100644 index 0000000..d04fac0 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Memcached.php @@ -0,0 +1,216 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +class Memcached extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 11211, + 'expire' => 0, + 'timeout' => 0, // 超时时间(单位:毫秒) + 'prefix' => '', + 'username' => '', //账号 + 'password' => '', //密码 + 'option' => [], + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct($options = []) + { + if (!extension_loaded('memcached')) { + throw new \BadFunctionCallException('not support: memcached'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $this->handler = new \Memcached; + + if (!empty($this->options['option'])) { + $this->handler->setOptions($this->options['option']); + } + + // 设置连接超时时间(单位:毫秒) + if ($this->options['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->options['timeout']); + } + + // 支持集群 + $hosts = explode(',', $this->options['host']); + $ports = explode(',', $this->options['port']); + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + + $this->handler->addServers($servers); + + if ('' != $this->options['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->options['username'], $this->options['password']); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + + return $this->handler->get($key) ? true : false; + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $result = $this->handler->get($this->getCacheKey($name)); + + return false !== $result ? $this->unserialize($result) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return bool + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->handler->set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + if ($this->handler->get($key)) { + return $this->handler->increment($key, $step); + } + + return $this->handler->set($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + $value = $this->handler->get($key) - $step; + $res = $this->handler->set($key, $value); + + return !$res ? false : $value; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @param bool|false $ttl + * @return bool + */ + public function rm($name, $ttl = false) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return false === $ttl ? + $this->handler->delete($key) : + $this->handler->delete($key, $ttl); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return bool + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + + $this->handler->deleteMulti($keys); + $this->rm('tag_' . md5($tag)); + + return true; + } + + $this->writeTimes++; + + return $this->handler->flush(); + } +} diff --git a/thinkphp/library/think/cache/driver/Redis.php b/thinkphp/library/think/cache/driver/Redis.php new file mode 100644 index 0000000..b924ec3 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Redis.php @@ -0,0 +1,218 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Redis缓存驱动,适合单机部署、有前端代理实现高可用的场景,性能最好 + * 有需要在业务层实现读写分离、或者使用RedisCluster的需求,请使用Redisd驱动 + * + * 要求安装phpredis扩展:https://github.com/nicolasff/phpredis + * @author 尘缘 <130775@qq.com> + */ +class Redis extends Driver +{ + protected $options = [ + 'host' => '127.0.0.1', + 'port' => 6379, + 'password' => '', + 'select' => 0, + 'timeout' => 0, + 'expire' => 0, + 'persistent' => false, + 'prefix' => '', + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + */ + public function __construct($options = []) + { + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + if ($this->options['persistent']) { + $this->handler->pconnect($this->options['host'], $this->options['port'], $this->options['timeout'], 'persistent_id_' . $this->options['select']); + } else { + $this->handler->connect($this->options['host'], $this->options['port'], $this->options['timeout']); + } + + if ('' != $this->options['password']) { + $this->handler->auth($this->options['password']); + } + + if (0 != $this->options['select']) { + $this->handler->select($this->options['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->options as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication', 'parameters'])) { + $params[$key] = $val; + unset($this->options[$key]); + } + } + + $this->handler = new \Predis\Client($this->options, $params); + + $this->options['prefix'] = ''; + } else { + throw new \BadFunctionCallException('not support: redis'); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + return $this->handler->exists($this->getCacheKey($name)); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $value = $this->handler->get($this->getCacheKey($name)); + + if (is_null($value) || false === $value) { + return $default; + } + + return $this->unserialize($value); + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + + $value = $this->serialize($value); + + if ($expire) { + $result = $this->handler->setex($key, $expire, $value); + } else { + $result = $this->handler->set($key, $value); + } + + isset($first) && $this->setTagItem($key); + + return $result; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return $this->handler->incrby($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return $this->handler->decrby($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return $this->handler->delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + + foreach ($keys as $key) { + $this->handler->delete($key); + } + + $this->rm('tag_' . md5($tag)); + return true; + } + + $this->writeTimes++; + + return $this->handler->flushDB(); + } + +} diff --git a/thinkphp/library/think/cache/driver/Sqlite.php b/thinkphp/library/think/cache/driver/Sqlite.php new file mode 100644 index 0000000..7e78ec1 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Sqlite.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Sqlite缓存驱动 + * @author liu21st + */ +class Sqlite extends Driver +{ + protected $options = [ + 'db' => ':memory:', + 'table' => 'sharedmemory', + 'prefix' => '', + 'expire' => 0, + 'persistent' => false, + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!extension_loaded('sqlite')) { + throw new \BadFunctionCallException('not support: sqlite'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + + $func = $this->options['persistent'] ? 'sqlite_popen' : 'sqlite_open'; + + $this->handler = $func($this->options['db']); + } + + /** + * 获取实际的缓存标识 + * @access public + * @param string $name 缓存名 + * @return string + */ + protected function getCacheKey($name) + { + return $this->options['prefix'] . sqlite_escape_string($name); + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; + $result = sqlite_query($this->handler, $sql); + + return sqlite_num_rows($result); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $name = $this->getCacheKey($name); + + $sql = 'SELECT value FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\' AND (expire=0 OR expire >' . time() . ') LIMIT 1'; + + $result = sqlite_query($this->handler, $sql); + + if (sqlite_num_rows($result)) { + $content = sqlite_fetch_single($result); + if (function_exists('gzcompress')) { + //启用数据压缩 + $content = gzuncompress($content); + } + + return $this->unserialize($content); + } + + return $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + $name = $this->getCacheKey($name); + + $value = sqlite_escape_string($this->serialize($value)); + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($expire instanceof \DateTime) { + $expire = $expire->getTimestamp(); + } else { + $expire = (0 == $expire) ? 0 : (time() + $expire); //缓存有效期为0表示永久缓存 + } + + if (function_exists('gzcompress')) { + //数据压缩 + $value = gzcompress($value, 3); + } + + if ($this->tag) { + $tag = $this->tag; + $this->tag = null; + } else { + $tag = ''; + } + + $sql = 'REPLACE INTO ' . $this->options['table'] . ' (var, value, expire, tag) VALUES (\'' . $name . '\', \'' . $value . '\', \'' . $expire . '\', \'' . $tag . '\')'; + + if (sqlite_query($this->handler, $sql)) { + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) + $step; + } else { + $value = $step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + if ($this->has($name)) { + $value = $this->get($name) - $step; + } else { + $value = -$step; + } + + return $this->set($name, $value, 0) ? $value : false; + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + $name = $this->getCacheKey($name); + + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE var=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + + return true; + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $name = sqlite_escape_string($tag); + $sql = 'DELETE FROM ' . $this->options['table'] . ' WHERE tag=\'' . $name . '\''; + sqlite_query($this->handler, $sql); + return true; + } + + $this->writeTimes++; + + $sql = 'DELETE FROM ' . $this->options['table']; + + sqlite_query($this->handler, $sql); + + return true; + } +} diff --git a/thinkphp/library/think/cache/driver/Wincache.php b/thinkphp/library/think/cache/driver/Wincache.php new file mode 100644 index 0000000..10966e7 --- /dev/null +++ b/thinkphp/library/think/cache/driver/Wincache.php @@ -0,0 +1,174 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Wincache缓存驱动 + * @author liu21st + */ +class Wincache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('wincache_ucache_info')) { + throw new \BadFunctionCallException('not support: WinCache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_exists($key) ? $this->unserialize(wincache_ucache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + if (wincache_ucache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return wincache_ucache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return wincache_ucache_delete($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + wincache_ucache_delete($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } else { + $this->writeTimes++; + return wincache_ucache_clear(); + } + } + +} diff --git a/thinkphp/library/think/cache/driver/Xcache.php b/thinkphp/library/think/cache/driver/Xcache.php new file mode 100644 index 0000000..6d1bf3f --- /dev/null +++ b/thinkphp/library/think/cache/driver/Xcache.php @@ -0,0 +1,177 @@ + +// +---------------------------------------------------------------------- + +namespace think\cache\driver; + +use think\cache\Driver; + +/** + * Xcache缓存驱动 + * @author liu21st + */ +class Xcache extends Driver +{ + protected $options = [ + 'prefix' => '', + 'expire' => 0, + 'serialize' => true, + ]; + + /** + * 架构函数 + * @access public + * @param array $options 缓存参数 + * @throws \BadFunctionCallException + */ + public function __construct($options = []) + { + if (!function_exists('xcache_info')) { + throw new \BadFunctionCallException('not support: Xcache'); + } + + if (!empty($options)) { + $this->options = array_merge($this->options, $options); + } + } + + /** + * 判断缓存 + * @access public + * @param string $name 缓存变量名 + * @return bool + */ + public function has($name) + { + $key = $this->getCacheKey($name); + + return xcache_isset($key); + } + + /** + * 读取缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $default 默认值 + * @return mixed + */ + public function get($name, $default = false) + { + $this->readTimes++; + + $key = $this->getCacheKey($name); + + return xcache_isset($key) ? $this->unserialize(xcache_get($key)) : $default; + } + + /** + * 写入缓存 + * @access public + * @param string $name 缓存变量名 + * @param mixed $value 存储数据 + * @param integer|\DateTime $expire 有效时间(秒) + * @return boolean + */ + public function set($name, $value, $expire = null) + { + $this->writeTimes++; + + if (is_null($expire)) { + $expire = $this->options['expire']; + } + + if ($this->tag && !$this->has($name)) { + $first = true; + } + + $key = $this->getCacheKey($name); + $expire = $this->getExpireTime($expire); + $value = $this->serialize($value); + + if (xcache_set($key, $value, $expire)) { + isset($first) && $this->setTagItem($key); + return true; + } + + return false; + } + + /** + * 自增缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function inc($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return xcache_inc($key, $step); + } + + /** + * 自减缓存(针对数值缓存) + * @access public + * @param string $name 缓存变量名 + * @param int $step 步长 + * @return false|int + */ + public function dec($name, $step = 1) + { + $this->writeTimes++; + + $key = $this->getCacheKey($name); + + return xcache_dec($key, $step); + } + + /** + * 删除缓存 + * @access public + * @param string $name 缓存变量名 + * @return boolean + */ + public function rm($name) + { + $this->writeTimes++; + + return xcache_unset($this->getCacheKey($name)); + } + + /** + * 清除缓存 + * @access public + * @param string $tag 标签名 + * @return boolean + */ + public function clear($tag = null) + { + if ($tag) { + // 指定标签清除 + $keys = $this->getTagItem($tag); + foreach ($keys as $key) { + xcache_unset($key); + } + $this->rm('tag_' . md5($tag)); + return true; + } + + $this->writeTimes++; + + if (function_exists('xcache_unset_by_prefix')) { + return xcache_unset_by_prefix($this->options['prefix']); + } else { + return false; + } + } +} diff --git a/thinkphp/library/think/config/driver/Ini.php b/thinkphp/library/think/config/driver/Ini.php new file mode 100644 index 0000000..b2a647d --- /dev/null +++ b/thinkphp/library/think/config/driver/Ini.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Ini +{ + protected $config; + + public function __construct($config) + { + $this->config = $config; + } + + public function parse() + { + if (is_file($this->config)) { + return parse_ini_file($this->config, true); + } else { + return parse_ini_string($this->config, true); + } + } +} diff --git a/thinkphp/library/think/config/driver/Json.php b/thinkphp/library/think/config/driver/Json.php new file mode 100644 index 0000000..0d77c8e --- /dev/null +++ b/thinkphp/library/think/config/driver/Json.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Json +{ + protected $config; + + public function __construct($config) + { + if (is_file($config)) { + $config = file_get_contents($config); + } + + $this->config = $config; + } + + public function parse() + { + return json_decode($this->config, true); + } +} diff --git a/thinkphp/library/think/config/driver/Xml.php b/thinkphp/library/think/config/driver/Xml.php new file mode 100644 index 0000000..9d69633 --- /dev/null +++ b/thinkphp/library/think/config/driver/Xml.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\config\driver; + +class Xml +{ + protected $config; + + public function __construct($config) + { + $this->config = $config; + } + + public function parse() + { + if (is_file($this->config)) { + $content = simplexml_load_file($this->config); + } else { + $content = simplexml_load_string($this->config); + } + + $result = (array) $content; + foreach ($result as $key => $val) { + if (is_object($val)) { + $result[$key] = (array) $val; + } + } + + return $result; + } +} diff --git a/thinkphp/library/think/console/Command.php b/thinkphp/library/think/console/Command.php new file mode 100644 index 0000000..a208e7b --- /dev/null +++ b/thinkphp/library/think/console/Command.php @@ -0,0 +1,482 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\Console; +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Command +{ + + /** @var Console */ + private $console; + private $name; + private $aliases = []; + private $definition; + private $help; + private $description; + private $ignoreValidationErrors = false; + private $consoleDefinitionMerged = false; + private $consoleDefinitionMergedWithArgs = false; + private $code; + private $synopsis = []; + private $usages = []; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** + * 构造方法 + * @param string|null $name 命令名称,如果没有设置则比如在 configure() 里设置 + * @throws \LogicException + * @api + */ + public function __construct($name = null) + { + $this->definition = new Definition(); + + if (null !== $name) { + $this->setName($name); + } + + $this->configure(); + + if (!$this->name) { + throw new \LogicException(sprintf('The command defined in "%s" cannot have an empty name.', get_class($this))); + } + } + + /** + * 忽略验证错误 + */ + public function ignoreValidationErrors() + { + $this->ignoreValidationErrors = true; + } + + /** + * 设置控制台 + * @param Console $console + */ + public function setConsole(Console $console = null) + { + $this->console = $console; + } + + /** + * 获取控制台 + * @return Console + * @api + */ + public function getConsole() + { + return $this->console; + } + + /** + * 是否有效 + * @return bool + */ + public function isEnabled() + { + return true; + } + + /** + * 配置指令 + */ + protected function configure() + { + } + + /** + * 执行指令 + * @param Input $input + * @param Output $output + * @return null|int + * @throws \LogicException + * @see setCode() + */ + protected function execute(Input $input, Output $output) + { + throw new \LogicException('You must override the execute() method in the concrete command class.'); + } + + /** + * 用户验证 + * @param Input $input + * @param Output $output + */ + protected function interact(Input $input, Output $output) + { + } + + /** + * 初始化 + * @param Input $input An InputInterface instance + * @param Output $output An OutputInterface instance + */ + protected function initialize(Input $input, Output $output) + { + } + + /** + * 执行 + * @param Input $input + * @param Output $output + * @return int + * @throws \Exception + * @see setCode() + * @see execute() + */ + public function run(Input $input, Output $output) + { + $this->input = $input; + $this->output = $output; + + $this->getSynopsis(true); + $this->getSynopsis(false); + + $this->mergeConsoleDefinition(); + + try { + $input->bind($this->definition); + } catch (\Exception $e) { + if (!$this->ignoreValidationErrors) { + throw $e; + } + } + + $this->initialize($input, $output); + + if ($input->isInteractive()) { + $this->interact($input, $output); + } + + $input->validate(); + + if ($this->code) { + $statusCode = call_user_func($this->code, $input, $output); + } else { + $statusCode = $this->execute($input, $output); + } + + return is_numeric($statusCode) ? (int) $statusCode : 0; + } + + /** + * 设置执行代码 + * @param callable $code callable(InputInterface $input, OutputInterface $output) + * @return Command + * @throws \InvalidArgumentException + * @see execute() + */ + public function setCode(callable $code) + { + if (!is_callable($code)) { + throw new \InvalidArgumentException('Invalid callable provided to Command::setCode.'); + } + + if (PHP_VERSION_ID >= 50400 && $code instanceof \Closure) { + $r = new \ReflectionFunction($code); + if (null === $r->getClosureThis()) { + $code = \Closure::bind($code, $this); + } + } + + $this->code = $code; + + return $this; + } + + /** + * 合并参数定义 + * @param bool $mergeArgs + */ + public function mergeConsoleDefinition($mergeArgs = true) + { + if (null === $this->console + || (true === $this->consoleDefinitionMerged + && ($this->consoleDefinitionMergedWithArgs || !$mergeArgs)) + ) { + return; + } + + if ($mergeArgs) { + $currentArguments = $this->definition->getArguments(); + $this->definition->setArguments($this->console->getDefinition()->getArguments()); + $this->definition->addArguments($currentArguments); + } + + $this->definition->addOptions($this->console->getDefinition()->getOptions()); + + $this->consoleDefinitionMerged = true; + if ($mergeArgs) { + $this->consoleDefinitionMergedWithArgs = true; + } + } + + /** + * 设置参数定义 + * @param array|Definition $definition + * @return Command + * @api + */ + public function setDefinition($definition) + { + if ($definition instanceof Definition) { + $this->definition = $definition; + } else { + $this->definition->setDefinition($definition); + } + + $this->consoleDefinitionMerged = false; + + return $this; + } + + /** + * 获取参数定义 + * @return Definition + * @api + */ + public function getDefinition() + { + return $this->definition; + } + + /** + * 获取当前指令的参数定义 + * @return Definition + */ + public function getNativeDefinition() + { + return $this->getDefinition(); + } + + /** + * 添加参数 + * @param string $name 名称 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addArgument($name, $mode = null, $description = '', $default = null) + { + $this->definition->addArgument(new Argument($name, $mode, $description, $default)); + + return $this; + } + + /** + * 添加选项 + * @param string $name 选项名称 + * @param string $shortcut 别名 + * @param int $mode 类型 + * @param string $description 描述 + * @param mixed $default 默认值 + * @return Command + */ + public function addOption($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + $this->definition->addOption(new Option($name, $shortcut, $mode, $description, $default)); + + return $this; + } + + /** + * 设置指令名称 + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function setName($name) + { + $this->validateName($name); + + $this->name = $name; + + return $this; + } + + /** + * 获取指令名称 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 设置描述 + * @param string $description + * @return Command + */ + public function setDescription($description) + { + $this->description = $description; + + return $this; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 设置帮助信息 + * @param string $help + * @return Command + */ + public function setHelp($help) + { + $this->help = $help; + + return $this; + } + + /** + * 获取帮助信息 + * @return string + */ + public function getHelp() + { + return $this->help; + } + + /** + * 描述信息 + * @return string + */ + public function getProcessedHelp() + { + $name = $this->name; + + $placeholders = [ + '%command.name%', + '%command.full_name%', + ]; + $replacements = [ + $name, + $_SERVER['PHP_SELF'] . ' ' . $name, + ]; + + return str_replace($placeholders, $replacements, $this->getHelp()); + } + + /** + * 设置别名 + * @param string[] $aliases + * @return Command + * @throws \InvalidArgumentException + */ + public function setAliases($aliases) + { + if (!is_array($aliases) && !$aliases instanceof \Traversable) { + throw new \InvalidArgumentException('$aliases must be an array or an instance of \Traversable'); + } + + foreach ($aliases as $alias) { + $this->validateName($alias); + } + + $this->aliases = $aliases; + + return $this; + } + + /** + * 获取别名 + * @return array + */ + public function getAliases() + { + return $this->aliases; + } + + /** + * 获取简介 + * @param bool $short 是否简单的 + * @return string + */ + public function getSynopsis($short = false) + { + $key = $short ? 'short' : 'long'; + + if (!isset($this->synopsis[$key])) { + $this->synopsis[$key] = trim(sprintf('%s %s', $this->name, $this->definition->getSynopsis($short))); + } + + return $this->synopsis[$key]; + } + + /** + * 添加用法介绍 + * @param string $usage + * @return $this + */ + public function addUsage($usage) + { + if (0 !== strpos($usage, $this->name)) { + $usage = sprintf('%s %s', $this->name, $usage); + } + + $this->usages[] = $usage; + + return $this; + } + + /** + * 获取用法介绍 + * @return array + */ + public function getUsages() + { + return $this->usages; + } + + /** + * 验证指令名称 + * @param string $name + * @throws \InvalidArgumentException + */ + private function validateName($name) + { + if (!preg_match('/^[^\:]++(\:[^\:]++)*$/', $name)) { + throw new \InvalidArgumentException(sprintf('Command name "%s" is invalid.', $name)); + } + } + + /** + * 输出表格 + * @param Table $table + * @return string + */ + protected function table(Table $table) + { + $content = $table->render(); + $this->output->writeln($content); + return $content; + } +} diff --git a/thinkphp/library/think/console/Input.php b/thinkphp/library/think/console/Input.php new file mode 100644 index 0000000..2482dfd --- /dev/null +++ b/thinkphp/library/think/console/Input.php @@ -0,0 +1,464 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use think\console\input\Argument; +use think\console\input\Definition; +use think\console\input\Option; + +class Input +{ + + /** + * @var Definition + */ + protected $definition; + + /** + * @var Option[] + */ + protected $options = []; + + /** + * @var Argument[] + */ + protected $arguments = []; + + protected $interactive = true; + + private $tokens; + private $parsed; + + public function __construct($argv = null) + { + if (null === $argv) { + $argv = $_SERVER['argv']; + // 去除命令名 + array_shift($argv); + } + + $this->tokens = $argv; + + $this->definition = new Definition(); + } + + protected function setTokens(array $tokens) + { + $this->tokens = $tokens; + } + + /** + * 绑定实例 + * @param Definition $definition A InputDefinition instance + */ + public function bind(Definition $definition) + { + $this->arguments = []; + $this->options = []; + $this->definition = $definition; + + $this->parse(); + } + + /** + * 解析参数 + */ + protected function parse() + { + $parseOptions = true; + $this->parsed = $this->tokens; + while (null !== $token = array_shift($this->parsed)) { + if ($parseOptions && '' == $token) { + $this->parseArgument($token); + } elseif ($parseOptions && '--' == $token) { + $parseOptions = false; + } elseif ($parseOptions && 0 === strpos($token, '--')) { + $this->parseLongOption($token); + } elseif ($parseOptions && '-' === $token[0] && '-' !== $token) { + $this->parseShortOption($token); + } else { + $this->parseArgument($token); + } + } + } + + /** + * 解析短选项 + * @param string $token 当前的指令. + */ + private function parseShortOption($token) + { + $name = substr($token, 1); + + if (strlen($name) > 1) { + if ($this->definition->hasShortcut($name[0]) + && $this->definition->getOptionForShortcut($name[0])->acceptValue() + ) { + $this->addShortOption($name[0], substr($name, 1)); + } else { + $this->parseShortOptionSet($name); + } + } else { + $this->addShortOption($name, null); + } + } + + /** + * 解析短选项 + * @param string $name 当前指令 + * @throws \RuntimeException + */ + private function parseShortOptionSet($name) + { + $len = strlen($name); + for ($i = 0; $i < $len; ++$i) { + if (!$this->definition->hasShortcut($name[$i])) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $name[$i])); + } + + $option = $this->definition->getOptionForShortcut($name[$i]); + if ($option->acceptValue()) { + $this->addLongOption($option->getName(), $i === $len - 1 ? null : substr($name, $i + 1)); + + break; + } else { + $this->addLongOption($option->getName(), null); + } + } + } + + /** + * 解析完整选项 + * @param string $token 当前指令 + */ + private function parseLongOption($token) + { + $name = substr($token, 2); + + if (false !== $pos = strpos($name, '=')) { + $this->addLongOption(substr($name, 0, $pos), substr($name, $pos + 1)); + } else { + $this->addLongOption($name, null); + } + } + + /** + * 解析参数 + * @param string $token 当前指令 + * @throws \RuntimeException + */ + private function parseArgument($token) + { + $c = count($this->arguments); + + if ($this->definition->hasArgument($c)) { + $arg = $this->definition->getArgument($c); + + $this->arguments[$arg->getName()] = $arg->isArray() ? [$token] : $token; + + } elseif ($this->definition->hasArgument($c - 1) && $this->definition->getArgument($c - 1)->isArray()) { + $arg = $this->definition->getArgument($c - 1); + + $this->arguments[$arg->getName()][] = $token; + } else { + throw new \RuntimeException('Too many arguments.'); + } + } + + /** + * 添加一个短选项的值 + * @param string $shortcut 短名称 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addShortOption($shortcut, $value) + { + if (!$this->definition->hasShortcut($shortcut)) { + throw new \RuntimeException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + $this->addLongOption($this->definition->getOptionForShortcut($shortcut)->getName(), $value); + } + + /** + * 添加一个完整选项的值 + * @param string $name 选项名 + * @param mixed $value 值 + * @throws \RuntimeException + */ + private function addLongOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \RuntimeException(sprintf('The "--%s" option does not exist.', $name)); + } + + $option = $this->definition->getOption($name); + + if (false === $value) { + $value = null; + } + + if (null !== $value && !$option->acceptValue()) { + throw new \RuntimeException(sprintf('The "--%s" option does not accept a value.', $name, $value)); + } + + if (null === $value && $option->acceptValue() && count($this->parsed)) { + $next = array_shift($this->parsed); + if (isset($next[0]) && '-' !== $next[0]) { + $value = $next; + } elseif (empty($next)) { + $value = ''; + } else { + array_unshift($this->parsed, $next); + } + } + + if (null === $value) { + if ($option->isValueRequired()) { + throw new \RuntimeException(sprintf('The "--%s" option requires a value.', $name)); + } + + if (!$option->isArray()) { + $value = $option->isValueOptional() ? $option->getDefault() : true; + } + } + + if ($option->isArray()) { + $this->options[$name][] = $value; + } else { + $this->options[$name] = $value; + } + } + + /** + * 获取第一个参数 + * @return string|null + */ + public function getFirstArgument() + { + foreach ($this->tokens as $token) { + if ($token && '-' === $token[0]) { + continue; + } + + return $token; + } + return; + } + + /** + * 检查原始参数是否包含某个值 + * @param string|array $values 需要检查的值 + * @return bool + */ + public function hasParameterOption($values) + { + $values = (array) $values; + + foreach ($this->tokens as $token) { + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + return true; + } + } + } + + return false; + } + + /** + * 获取原始选项的值 + * @param string|array $values 需要检查的值 + * @param mixed $default 默认值 + * @return mixed The option value + */ + public function getParameterOption($values, $default = false) + { + $values = (array) $values; + $tokens = $this->tokens; + + while (0 < count($tokens)) { + $token = array_shift($tokens); + + foreach ($values as $value) { + if ($token === $value || 0 === strpos($token, $value . '=')) { + if (false !== $pos = strpos($token, '=')) { + return substr($token, $pos + 1); + } + + return array_shift($tokens); + } + } + } + + return $default; + } + + /** + * 验证输入 + * @throws \RuntimeException + */ + public function validate() + { + if (count($this->arguments) < $this->definition->getArgumentRequiredCount()) { + throw new \RuntimeException('Not enough arguments.'); + } + } + + /** + * 检查输入是否是交互的 + * @return bool + */ + public function isInteractive() + { + return $this->interactive; + } + + /** + * 设置输入的交互 + * @param bool + */ + public function setInteractive($interactive) + { + $this->interactive = (bool) $interactive; + } + + /** + * 获取所有的参数 + * @return Argument[] + */ + public function getArguments() + { + return array_merge($this->definition->getArgumentDefaults(), $this->arguments); + } + + /** + * 根据名称获取参数 + * @param string $name 参数名 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + return isset($this->arguments[$name]) ? $this->arguments[$name] : $this->definition->getArgument($name) + ->getDefault(); + } + + /** + * 设置参数的值 + * @param string $name 参数名 + * @param string $value 值 + * @throws \InvalidArgumentException + */ + public function setArgument($name, $value) + { + if (!$this->definition->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $this->arguments[$name] = $value; + } + + /** + * 检查是否存在某个参数 + * @param string|int $name 参数名或位置 + * @return bool + */ + public function hasArgument($name) + { + return $this->definition->hasArgument($name); + } + + /** + * 获取所有的选项 + * @return Option[] + */ + public function getOptions() + { + return array_merge($this->definition->getOptionDefaults(), $this->options); + } + + /** + * 获取选项值 + * @param string $name 选项名称 + * @return mixed + * @throws \InvalidArgumentException + */ + public function getOption($name) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + return isset($this->options[$name]) ? $this->options[$name] : $this->definition->getOption($name)->getDefault(); + } + + /** + * 设置选项值 + * @param string $name 选项名 + * @param string|bool $value 值 + * @throws \InvalidArgumentException + */ + public function setOption($name, $value) + { + if (!$this->definition->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" option does not exist.', $name)); + } + + $this->options[$name] = $value; + } + + /** + * 是否有某个选项 + * @param string $name 选项名 + * @return bool + */ + public function hasOption($name) + { + return $this->definition->hasOption($name) && isset($this->options[$name]); + } + + /** + * 转义指令 + * @param string $token + * @return string + */ + public function escapeToken($token) + { + return preg_match('{^[\w-]+$}', $token) ? $token : escapeshellarg($token); + } + + /** + * 返回传递给命令的参数的字符串 + * @return string + */ + public function __toString() + { + $tokens = array_map(function ($token) { + if (preg_match('{^(-[^=]+=)(.+)}', $token, $match)) { + return $match[1] . $this->escapeToken($match[2]); + } + + if ($token && '-' !== $token[0]) { + return $this->escapeToken($token); + } + + return $token; + }, $this->tokens); + + return implode(' ', $tokens); + } +} diff --git a/thinkphp/library/think/console/LICENSE b/thinkphp/library/think/console/LICENSE new file mode 100644 index 0000000..0abe056 --- /dev/null +++ b/thinkphp/library/think/console/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2016 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/thinkphp/library/think/console/Output.php b/thinkphp/library/think/console/Output.php new file mode 100644 index 0000000..65dc9fb --- /dev/null +++ b/thinkphp/library/think/console/Output.php @@ -0,0 +1,222 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +use Exception; +use think\console\output\Ask; +use think\console\output\Descriptor; +use think\console\output\driver\Buffer; +use think\console\output\driver\Console; +use think\console\output\driver\Nothing; +use think\console\output\Question; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +/** + * Class Output + * @package think\console + * + * @see \think\console\output\driver\Console::setDecorated + * @method void setDecorated($decorated) + * + * @see \think\console\output\driver\Buffer::fetch + * @method string fetch() + * + * @method void info($message) + * @method void error($message) + * @method void comment($message) + * @method void warning($message) + * @method void highlight($message) + * @method void question($message) + */ +class Output +{ + const VERBOSITY_QUIET = 0; + const VERBOSITY_NORMAL = 1; + const VERBOSITY_VERBOSE = 2; + const VERBOSITY_VERY_VERBOSE = 3; + const VERBOSITY_DEBUG = 4; + + const OUTPUT_NORMAL = 0; + const OUTPUT_RAW = 1; + const OUTPUT_PLAIN = 2; + + private $verbosity = self::VERBOSITY_NORMAL; + + /** @var Buffer|Console|Nothing */ + private $handle = null; + + protected $styles = [ + 'info', + 'error', + 'comment', + 'question', + 'highlight', + 'warning' + ]; + + public function __construct($driver = 'console') + { + $class = '\\think\\console\\output\\driver\\' . ucwords($driver); + + $this->handle = new $class($this); + } + + public function ask(Input $input, $question, $default = null, $validator = null) + { + $question = new Question($question, $default); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function askHidden(Input $input, $question, $validator = null) + { + $question = new Question($question); + + $question->setHidden(true); + $question->setValidator($validator); + + return $this->askQuestion($input, $question); + } + + public function confirm(Input $input, $question, $default = true) + { + return $this->askQuestion($input, new Confirmation($question, $default)); + } + + /** + * {@inheritdoc} + */ + public function choice(Input $input, $question, array $choices, $default = null) + { + if (null !== $default) { + $values = array_flip($choices); + $default = $values[$default]; + } + + return $this->askQuestion($input, new Choice($question, $choices, $default)); + } + + protected function askQuestion(Input $input, Question $question) + { + $ask = new Ask($input, $this, $question); + $answer = $ask->run(); + + if ($input->isInteractive()) { + $this->newLine(); + } + + return $answer; + } + + protected function block($style, $message) + { + $this->writeln("<{$style}>{$message}"); + } + + /** + * 输出空行 + * @param int $count + */ + public function newLine($count = 1) + { + $this->write(str_repeat(PHP_EOL, $count)); + } + + /** + * 输出信息并换行 + * @param string $messages + * @param int $type + */ + public function writeln($messages, $type = self::OUTPUT_NORMAL) + { + $this->write($messages, true, $type); + } + + /** + * 输出信息 + * @param string $messages + * @param bool $newline + * @param int $type + */ + public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL) + { + $this->handle->write($messages, $newline, $type); + } + + public function renderException(\Exception $e) + { + $this->handle->renderException($e); + } + + /** + * {@inheritdoc} + */ + public function setVerbosity($level) + { + $this->verbosity = (int) $level; + } + + /** + * {@inheritdoc} + */ + public function getVerbosity() + { + return $this->verbosity; + } + + public function isQuiet() + { + return self::VERBOSITY_QUIET === $this->verbosity; + } + + public function isVerbose() + { + return self::VERBOSITY_VERBOSE <= $this->verbosity; + } + + public function isVeryVerbose() + { + return self::VERBOSITY_VERY_VERBOSE <= $this->verbosity; + } + + public function isDebug() + { + return self::VERBOSITY_DEBUG <= $this->verbosity; + } + + public function describe($object, array $options = []) + { + $descriptor = new Descriptor(); + $options = array_merge([ + 'raw_text' => false, + ], $options); + + $descriptor->describe($this, $object, $options); + } + + public function __call($method, $args) + { + if (in_array($method, $this->styles)) { + array_unshift($args, $method); + return call_user_func_array([$this, 'block'], $args); + } + + if ($this->handle && method_exists($this->handle, $method)) { + return call_user_func_array([$this->handle, $method], $args); + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } + +} diff --git a/thinkphp/library/think/console/Table.php b/thinkphp/library/think/console/Table.php new file mode 100644 index 0000000..9e28e26 --- /dev/null +++ b/thinkphp/library/think/console/Table.php @@ -0,0 +1,281 @@ + +// +---------------------------------------------------------------------- + +namespace think\console; + +class Table +{ + const ALIGN_LEFT = 1; + const ALIGN_RIGHT = 0; + const ALIGN_CENTER = 2; + + /** + * 头信息数据 + * @var array + */ + protected $header = []; + + /** + * 头部对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $headerAlign = 1; + + /** + * 表格数据(二维数组) + * @var array + */ + protected $rows = []; + + /** + * 单元格对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @var int + */ + protected $cellAlign = 1; + + /** + * 单元格宽度信息 + * @var array + */ + protected $colWidth = []; + + /** + * 表格输出样式 + * @var string + */ + protected $style = 'default'; + + /** + * 表格样式定义 + * @var array + */ + protected $format = [ + 'compact' => [], + 'default' => [ + 'top' => ['+', '-', '+', '+'], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['+', '-', '+', '+'], + 'bottom' => ['+', '-', '+', '+'], + 'cross-top' => ['+', '-', '-', '+'], + 'cross-bottom' => ['+', '-', '-', '+'], + ], + 'markdown' => [ + 'top' => [' ', ' ', ' ', ' '], + 'cell' => ['|', ' ', '|', '|'], + 'middle' => ['|', '-', '|', '|'], + 'bottom' => [' ', ' ', ' ', ' '], + 'cross-top' => ['|', ' ', ' ', '|'], + 'cross-bottom' => ['|', ' ', ' ', '|'], + ], + 'borderless' => [ + 'top' => ['=', '=', ' ', '='], + 'cell' => [' ', ' ', ' ', ' '], + 'middle' => ['=', '=', ' ', '='], + 'bottom' => ['=', '=', ' ', '='], + 'cross-top' => ['=', '=', ' ', '='], + 'cross-bottom' => ['=', '=', ' ', '='], + ], + 'box' => [ + 'top' => ['┌', '─', '┬', '┐'], + 'cell' => ['│', ' ', '│', '│'], + 'middle' => ['├', '─', '┼', '┤'], + 'bottom' => ['└', '─', '┴', '┘'], + 'cross-top' => ['├', '─', '┴', '┤'], + 'cross-bottom' => ['├', '─', '┬', '┤'], + ], + 'box-double' => [ + 'top' => ['╔', '═', '╤', '╗'], + 'cell' => ['║', ' ', '│', '║'], + 'middle' => ['╠', '─', '╪', '╣'], + 'bottom' => ['╚', '═', '╧', '╝'], + 'cross-top' => ['╠', '═', '╧', '╣'], + 'cross-bottom' => ['╠', '═', '╤', '╣'], + ], + ]; + + /** + * 设置表格头信息 以及对齐方式 + * @access public + * @param array $header 要输出的Header信息 + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setHeader(array $header, $align = self::ALIGN_LEFT) + { + $this->header = $header; + $this->headerAlign = $align; + + $this->checkColWidth($header); + } + + /** + * 设置输出表格数据 及对齐方式 + * @access public + * @param array $rows 要输出的表格数据(二维数组) + * @param int $align 对齐方式 默认1 ALGIN_LEFT 0 ALIGN_RIGHT 2 ALIGN_CENTER + * @return void + */ + public function setRows(array $rows, $align = self::ALIGN_LEFT) + { + $this->rows = $rows; + $this->cellAlign = $align; + + foreach ($rows as $row) { + $this->checkColWidth($row); + } + } + + /** + * 检查列数据的显示宽度 + * @access public + * @param mixed $row 行数据 + * @return void + */ + protected function checkColWidth($row) + { + if (is_array($row)) { + foreach ($row as $key => $cell) { + if (!isset($this->colWidth[$key]) || strlen($cell) > $this->colWidth[$key]) { + $this->colWidth[$key] = strlen($cell); + } + } + } + } + + /** + * 增加一行表格数据 + * @access public + * @param mixed $row 行数据 + * @param bool $first 是否在开头插入 + * @return void + */ + public function addRow($row, $first = false) + { + if ($first) { + array_unshift($this->rows, $row); + } else { + $this->rows[] = $row; + } + + $this->checkColWidth($row); + } + + /** + * 设置输出表格的样式 + * @access public + * @param string $style 样式名 + * @return void + */ + public function setStyle($style) + { + $this->style = isset($this->format[$style]) ? $style : 'default'; + } + + /** + * 输出分隔行 + * @access public + * @param string $pos 位置 + * @return string + */ + protected function renderSeparator($pos) + { + $style = $this->getStyle($pos); + $array = []; + + foreach ($this->colWidth as $width) { + $array[] = str_repeat($style[1], $width + 2); + } + + return $style[0] . implode($style[2], $array) . $style[3] . PHP_EOL; + } + + /** + * 输出表格头部 + * @access public + * @return string + */ + protected function renderHeader() + { + $style = $this->getStyle('cell'); + $content = $this->renderSeparator('top'); + + foreach ($this->header as $key => $header) { + $array[] = ' ' . str_pad($header, $this->colWidth[$key], $style[1], $this->headerAlign); + } + + if (!empty($array)) { + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + if ($this->rows) { + $content .= $this->renderSeparator('middle'); + } + } + + return $content; + } + + protected function getStyle($style) + { + if ($this->format[$this->style]) { + $style = $this->format[$this->style][$style]; + } else { + $style = [' ', ' ', ' ', ' ']; + } + + return $style; + } + + /** + * 输出表格 + * @access public + * @param array $dataList 表格数据 + * @return string + */ + public function render($dataList = []) + { + if ($dataList) { + $this->setRows($dataList); + } + + // 输出头部 + $content = $this->renderHeader(); + $style = $this->getStyle('cell'); + + if ($this->rows) { + foreach ($this->rows as $row) { + if (is_string($row) && '-' === $row) { + $content .= $this->renderSeparator('middle'); + } elseif (is_scalar($row)) { + $content .= $this->renderSeparator('cross-top'); + $array = str_pad($row, 3 * (count($this->colWidth) - 1) + array_reduce($this->colWidth, function ($a, $b) { + return $a + $b; + })); + + $content .= $style[0] . ' ' . $array . ' ' . $style[3] . PHP_EOL; + $content .= $this->renderSeparator('cross-bottom'); + } else { + $array = []; + + foreach ($row as $key => $val) { + $array[] = ' ' . str_pad($val, $this->colWidth[$key], ' ', $this->cellAlign); + } + + $content .= $style[0] . implode(' ' . $style[2], $array) . ' ' . $style[3] . PHP_EOL; + + } + } + } + + $content .= $this->renderSeparator('bottom'); + + return $content; + } +} diff --git a/thinkphp/library/think/console/bin/README.md b/thinkphp/library/think/console/bin/README.md new file mode 100644 index 0000000..9acc52f --- /dev/null +++ b/thinkphp/library/think/console/bin/README.md @@ -0,0 +1 @@ +console 工具使用 hiddeninput.exe 在 windows 上隐藏密码输入,该二进制文件由第三方提供,相关源码和其他细节可以在 [Hidden Input](https://github.com/Seldaek/hidden-input) 找到。 diff --git a/thinkphp/library/think/console/bin/hiddeninput.exe b/thinkphp/library/think/console/bin/hiddeninput.exe new file mode 100644 index 0000000..c8cf65e Binary files /dev/null and b/thinkphp/library/think/console/bin/hiddeninput.exe differ diff --git a/thinkphp/library/think/console/command/Build.php b/thinkphp/library/think/console/command/Build.php new file mode 100644 index 0000000..88a5bf8 --- /dev/null +++ b/thinkphp/library/think/console/command/Build.php @@ -0,0 +1,59 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; +use think\facade\Build as AppBuild; + +class Build extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('build') + ->setDefinition([ + new Option('config', null, Option::VALUE_OPTIONAL, "build.php path"), + new Option('module', null, Option::VALUE_OPTIONAL, "module name"), + ]) + ->setDescription('Build Application Dirs'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->hasOption('module')) { + AppBuild::module($input->getOption('module')); + $output->writeln("Successed"); + return; + } + + if ($input->hasOption('config')) { + $build = include $input->getOption('config'); + } else { + $build = include App::getAppPath() . 'build.php'; + } + + if (empty($build)) { + $output->writeln("Build Config Is Empty"); + return; + } + + AppBuild::run($build); + $output->writeln("Successed"); + + } +} diff --git a/thinkphp/library/think/console/command/Clear.php b/thinkphp/library/think/console/command/Clear.php new file mode 100644 index 0000000..1442575 --- /dev/null +++ b/thinkphp/library/think/console/command/Clear.php @@ -0,0 +1,70 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; +use think\facade\Cache; + +class Clear extends Command +{ + protected function configure() + { + // 指令配置 + $this + ->setName('clear') + ->addOption('path', 'd', Option::VALUE_OPTIONAL, 'path to clear', null) + ->addOption('cache', 'c', Option::VALUE_NONE, 'clear cache file') + ->addOption('route', 'u', Option::VALUE_NONE, 'clear route cache') + ->addOption('log', 'l', Option::VALUE_NONE, 'clear log file') + ->addOption('dir', 'r', Option::VALUE_NONE, 'clear empty dir') + ->setDescription('Clear runtime file'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getOption('route')) { + Cache::clear('route_cache'); + } else { + if ($input->getOption('cache')) { + $path = App::getRuntimePath() . 'cache'; + } elseif ($input->getOption('log')) { + $path = App::getRuntimePath() . 'log'; + } else { + $path = $input->getOption('path') ?: App::getRuntimePath(); + } + + $rmdir = $input->getOption('dir') ? true : false; + $this->clear(rtrim($path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, $rmdir); + } + + $output->writeln("Clear Successed"); + } + + protected function clear($path, $rmdir) + { + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if ('.' != $file && '..' != $file && is_dir($path . $file)) { + array_map('unlink', glob($path . $file . DIRECTORY_SEPARATOR . '*.*')); + if ($rmdir) { + rmdir($path . $file); + } + } elseif ('.gitignore' != $file && is_file($path . $file)) { + unlink($path . $file); + } + } + } +} diff --git a/thinkphp/library/think/console/command/Help.php b/thinkphp/library/think/console/command/Help.php new file mode 100644 index 0000000..f1b63b4 --- /dev/null +++ b/thinkphp/library/think/console/command/Help.php @@ -0,0 +1,68 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Help extends Command +{ + private $command; + + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->ignoreValidationErrors(); + + $this->setName('help')->setDefinition([ + new InputArgument('command_name', InputArgument::OPTIONAL, 'The command name', 'help'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command help'), + ])->setDescription('Displays help for a command')->setHelp(<<%command.name% command displays help for a given command: + + php %command.full_name% list + +To display the list of available commands, please use the list command. +EOF + ); + } + + /** + * Sets the command. + * @param Command $command The command to set + */ + public function setCommand(Command $command) + { + $this->command = $command; + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + if (null === $this->command) { + $this->command = $this->getConsole()->find($input->getArgument('command_name')); + } + + $output->describe($this->command, [ + 'raw_text' => $input->getOption('raw'), + ]); + + $this->command = null; + } +} diff --git a/thinkphp/library/think/console/command/Lists.php b/thinkphp/library/think/console/command/Lists.php new file mode 100644 index 0000000..6eb856c --- /dev/null +++ b/thinkphp/library/think/console/command/Lists.php @@ -0,0 +1,73 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; + +class Lists extends Command +{ + /** + * {@inheritdoc} + */ + protected function configure() + { + $this->setName('list')->setDefinition($this->createDefinition())->setDescription('Lists commands')->setHelp(<<%command.name% command lists all commands: + + php %command.full_name% + +You can also display the commands for a specific namespace: + + php %command.full_name% test + +It's also possible to get raw list of commands (useful for embedding command runner): + + php %command.full_name% --raw +EOF + ); + } + + /** + * {@inheritdoc} + */ + public function getNativeDefinition() + { + return $this->createDefinition(); + } + + /** + * {@inheritdoc} + */ + protected function execute(Input $input, Output $output) + { + $output->describe($this->getConsole(), [ + 'raw_text' => $input->getOption('raw'), + 'namespace' => $input->getArgument('namespace'), + ]); + } + + /** + * {@inheritdoc} + */ + private function createDefinition() + { + return new InputDefinition([ + new InputArgument('namespace', InputArgument::OPTIONAL, 'The namespace name'), + new InputOption('raw', null, InputOption::VALUE_NONE, 'To output raw command list'), + ]); + } +} diff --git a/thinkphp/library/think/console/command/Make.php b/thinkphp/library/think/console/command/Make.php new file mode 100644 index 0000000..2f20954 --- /dev/null +++ b/thinkphp/library/think/console/command/Make.php @@ -0,0 +1,110 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\facade\App; +use think\facade\Config; +use think\facade\Env; + +abstract class Make extends Command +{ + protected $type; + + abstract protected function getStub(); + + protected function configure() + { + $this->addArgument('name', Argument::REQUIRED, "The name of the class"); + } + + protected function execute(Input $input, Output $output) + { + + $name = trim($input->getArgument('name')); + + $classname = $this->getClassName($name); + + $pathname = $this->getPathName($classname); + + if (is_file($pathname)) { + $output->writeln('' . $this->type . ' already exists!'); + return false; + } + + if (!is_dir(dirname($pathname))) { + mkdir(dirname($pathname), 0755, true); + } + + file_put_contents($pathname, $this->buildClass($classname)); + + $output->writeln('' . $this->type . ' created successfully.'); + + } + + protected function buildClass($name) + { + $stub = file_get_contents($this->getStub()); + + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + + return str_replace(['{%className%}', '{%actionSuffix%}', '{%namespace%}', '{%app_namespace%}'], [ + $class, + Config::get('action_suffix'), + $namespace, + App::getNamespace(), + ], $stub); + } + + protected function getPathName($name) + { + $name = str_replace(App::getNamespace() . '\\', '', $name); + + return Env::get('app_path') . ltrim(str_replace('\\', '/', $name), '/') . '.php'; + } + + protected function getClassName($name) + { + $appNamespace = App::getNamespace(); + + if (strpos($name, $appNamespace . '\\') !== false) { + return $name; + } + + if (Config::get('app_multi_module')) { + if (strpos($name, '/')) { + list($module, $name) = explode('/', $name, 2); + } else { + $module = 'common'; + } + } else { + $module = null; + } + + if (strpos($name, '/') !== false) { + $name = str_replace('/', '\\', $name); + } + + return $this->getNamespace($appNamespace, $module) . '\\' . $name; + } + + protected function getNamespace($appNamespace, $module) + { + return $module ? ($appNamespace . '\\' . $module) : $appNamespace; + } + +} diff --git a/thinkphp/library/think/console/command/RouteList.php b/thinkphp/library/think/console/command/RouteList.php new file mode 100644 index 0000000..0405c31 --- /dev/null +++ b/thinkphp/library/think/console/command/RouteList.php @@ -0,0 +1,130 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\input\Option; +use think\console\Output; +use think\console\Table; +use think\Container; + +class RouteList extends Command +{ + protected $sortBy = [ + 'rule' => 0, + 'route' => 1, + 'method' => 2, + 'name' => 3, + 'domain' => 4, + ]; + + protected function configure() + { + $this->setName('route:list') + ->addArgument('style', Argument::OPTIONAL, "the style of the table.", 'default') + ->addOption('sort', 's', Option::VALUE_OPTIONAL, 'order by rule name.', 0) + ->addOption('more', 'm', Option::VALUE_NONE, 'show route options.') + ->setDescription('show route list.'); + } + + protected function execute(Input $input, Output $output) + { + $filename = Container::get('app')->getRuntimePath() . 'route_list.php'; + + if (is_file($filename)) { + unlink($filename); + } + + $content = $this->getRouteList(); + file_put_contents($filename, 'Route List' . PHP_EOL . $content); + } + + protected function getRouteList() + { + Container::get('route')->setTestMode(true); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + + if (is_array($rules)) { + Container::get('route')->import($rules); + } + } + } + + if (Container::get('config')->get('route_annotation')) { + $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix'); + + include Container::get('build')->buildRoute($suffix); + } + + $table = new Table(); + + if ($this->input->hasOption('more')) { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain', 'Option', 'Pattern']; + } else { + $header = ['Rule', 'Route', 'Method', 'Name', 'Domain']; + } + + $table->setHeader($header); + + $routeList = Container::get('route')->getRuleList(); + $rows = []; + + foreach ($routeList as $domain => $items) { + foreach ($items as $item) { + $item['route'] = $item['route'] instanceof \Closure ? '' : $item['route']; + + if ($this->input->hasOption('more')) { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain, json_encode($item['option']), json_encode($item['pattern'])]; + } else { + $item = [$item['rule'], $item['route'], $item['method'], $item['name'], $domain]; + } + + $rows[] = $item; + } + } + + if ($this->input->getOption('sort')) { + $sort = $this->input->getOption('sort'); + + if (isset($this->sortBy[$sort])) { + $sort = $this->sortBy[$sort]; + } + + uasort($rows, function ($a, $b) use ($sort) { + $itemA = isset($a[$sort]) ? $a[$sort] : null; + $itemB = isset($b[$sort]) ? $b[$sort] : null; + + return strcasecmp($itemA, $itemB); + }); + } + + $table->setRows($rows); + + if ($this->input->getArgument('style')) { + $style = $this->input->getArgument('style'); + $table->setStyle($style); + } + + return $this->table($table); + } + +} diff --git a/thinkphp/library/think/console/command/RunServer.php b/thinkphp/library/think/console/command/RunServer.php new file mode 100644 index 0000000..2e028dc --- /dev/null +++ b/thinkphp/library/think/console/command/RunServer.php @@ -0,0 +1,53 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\facade\App; + +class RunServer extends Command +{ + public function configure() + { + $this->setName('run') + ->addOption('host', 'H', Option::VALUE_OPTIONAL, + 'The host to server the application on', '127.0.0.1') + ->addOption('port', 'p', Option::VALUE_OPTIONAL, + 'The port to server the application on', 8000) + ->addOption('root', 'r', Option::VALUE_OPTIONAL, + 'The document root of the application', App::getRootPath() . 'public') + ->setDescription('PHP Built-in Server for ThinkPHP'); + } + + public function execute(Input $input, Output $output) + { + $host = $input->getOption('host'); + $port = $input->getOption('port'); + $root = $input->getOption('root'); + + $command = sprintf( + 'php -S %s:%d -t %s %s', + $host, + $port, + escapeshellarg($root), + escapeshellarg($root . DIRECTORY_SEPARATOR . 'router.php') + ); + + $output->writeln(sprintf('ThinkPHP Development server is started On ', $host, $port)); + $output->writeln(sprintf('You can exit with `CTRL-C`')); + $output->writeln(sprintf('Document root is: %s', $root)); + passthru($command); + } + +} diff --git a/thinkphp/library/think/console/command/Version.php b/thinkphp/library/think/console/command/Version.php new file mode 100644 index 0000000..ee7eca9 --- /dev/null +++ b/thinkphp/library/think/console/command/Version.php @@ -0,0 +1,31 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\facade\App; + +class Version extends Command +{ + protected function configure() + { + // 指令配置 + $this->setName('version') + ->setDescription('show thinkphp framework version'); + } + + protected function execute(Input $input, Output $output) + { + $output->writeln('v' . App::version()); + } +} diff --git a/thinkphp/library/think/console/command/make/Command.php b/thinkphp/library/think/console/command/make/Command.php new file mode 100644 index 0000000..b539eb2 --- /dev/null +++ b/thinkphp/library/think/console/command/make/Command.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Argument; +use think\facade\App; + +class Command extends Make +{ + protected $type = "Command"; + + protected function configure() + { + parent::configure(); + $this->setName('make:command') + ->addArgument('commandName', Argument::OPTIONAL, "The name of the command") + ->setDescription('Create a new command class'); + } + + protected function buildClass($name) + { + $commandName = $this->input->getArgument('commandName') ?: strtolower(basename($name)); + $namespace = trim(implode('\\', array_slice(explode('\\', $name), 0, -1)), '\\'); + + $class = str_replace($namespace . '\\', '', $name); + $stub = file_get_contents($this->getStub()); + + return str_replace(['{%commandName%}', '{%className%}', '{%namespace%}', '{%app_namespace%}'], [ + $commandName, + $class, + $namespace, + App::getNamespace(), + ], $stub); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'command.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return $appNamespace . '\\command'; + } + +} diff --git a/thinkphp/library/think/console/command/make/Controller.php b/thinkphp/library/think/console/command/make/Controller.php new file mode 100644 index 0000000..2a6ab77 --- /dev/null +++ b/thinkphp/library/think/console/command/make/Controller.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; +use think\console\input\Option; +use think\facade\Config; + +class Controller extends Make +{ + protected $type = "Controller"; + + protected function configure() + { + parent::configure(); + $this->setName('make:controller') + ->addOption('api', null, Option::VALUE_NONE, 'Generate an api controller class.') + ->addOption('plain', null, Option::VALUE_NONE, 'Generate an empty controller class.') + ->setDescription('Create a new resource controller class'); + } + + protected function getStub() + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + if ($this->input->getOption('api')) { + return $stubPath . 'controller.api.stub'; + } + + if ($this->input->getOption('plain')) { + return $stubPath . 'controller.plain.stub'; + } + + return $stubPath . 'controller.stub'; + } + + protected function getClassName($name) + { + return parent::getClassName($name) . (Config::get('controller_suffix') ? ucfirst(Config::get('url_controller_layer')) : ''); + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\controller'; + } + +} diff --git a/thinkphp/library/think/console/command/make/Middleware.php b/thinkphp/library/think/console/command/make/Middleware.php new file mode 100644 index 0000000..bfe821b --- /dev/null +++ b/thinkphp/library/think/console/command/make/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Middleware extends Make +{ + protected $type = "Middleware"; + + protected function configure() + { + parent::configure(); + $this->setName('make:middleware') + ->setDescription('Create a new middleware class'); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'middleware.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, 'http') . '\middleware'; + } +} diff --git a/thinkphp/library/think/console/command/make/Model.php b/thinkphp/library/think/console/command/make/Model.php new file mode 100644 index 0000000..03e6b3f --- /dev/null +++ b/thinkphp/library/think/console/command/make/Model.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Model extends Make +{ + protected $type = "Model"; + + protected function configure() + { + parent::configure(); + $this->setName('make:model') + ->setDescription('Create a new model class'); + } + + protected function getStub() + { + return __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR . 'model.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\model'; + } +} diff --git a/thinkphp/library/think/console/command/make/Validate.php b/thinkphp/library/think/console/command/make/Validate.php new file mode 100644 index 0000000..89830ad --- /dev/null +++ b/thinkphp/library/think/console/command/make/Validate.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\command\make; + +use think\console\command\Make; + +class Validate extends Make +{ + protected $type = "Validate"; + + protected function configure() + { + parent::configure(); + $this->setName('make:validate') + ->setDescription('Create a validate class'); + } + + protected function getStub() + { + $stubPath = __DIR__ . DIRECTORY_SEPARATOR . 'stubs' . DIRECTORY_SEPARATOR; + + return $stubPath . 'validate.stub'; + } + + protected function getNamespace($appNamespace, $module) + { + return parent::getNamespace($appNamespace, $module) . '\validate'; + } + +} diff --git a/thinkphp/library/think/console/command/make/stubs/command.stub b/thinkphp/library/think/console/command/make/stubs/command.stub new file mode 100644 index 0000000..d2c7c1e --- /dev/null +++ b/thinkphp/library/think/console/command/make/stubs/command.stub @@ -0,0 +1,24 @@ +setName('{%commandName%}'); + // 设置参数 + + } + + protected function execute(Input $input, Output $output) + { + // 指令输出 + $output->writeln('{%commandName%}'); + } +} diff --git a/thinkphp/library/think/console/command/make/stubs/controller.api.stub b/thinkphp/library/think/console/command/make/stubs/controller.api.stub new file mode 100644 index 0000000..54ec059 --- /dev/null +++ b/thinkphp/library/think/console/command/make/stubs/controller.api.stub @@ -0,0 +1,64 @@ + ['规则1','规则2'...] + * + * @var array + */ + protected $rule = []; + + /** + * 定义错误信息 + * 格式:'字段名.规则名' => '错误信息' + * + * @var array + */ + protected $message = []; +} diff --git a/thinkphp/library/think/console/command/optimize/Autoload.php b/thinkphp/library/think/console/command/optimize/Autoload.php new file mode 100644 index 0000000..b51fd25 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Autoload.php @@ -0,0 +1,279 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\Container; + +class Autoload extends Command +{ + protected function configure() + { + $this->setName('optimize:autoload') + ->setDescription('Optimizes PSR0 and PSR4 packages to be loaded with classmaps too, good for production.'); + } + + protected function execute(Input $input, Output $output) + { + + $classmapFile = <<getNamespace() . '\\' => realpath(rtrim($app->getAppPath())), + 'think\\' => $app->getThinkPath() . 'library/think', + 'traits\\' => $app->getThinkPath() . 'library/traits', + '' => realpath(rtrim($app->getRootPath() . 'extend')), + ]; + + krsort($namespacesToScan); + $classMap = []; + foreach ($namespacesToScan as $namespace => $dir) { + + if (!is_dir($dir)) { + continue; + } + + $namespaceFilter = '' === $namespace ? null : $namespace; + $classMap = $this->addClassMapCode($dir, $namespaceFilter, $classMap); + } + + ksort($classMap); + foreach ($classMap as $class => $code) { + $classmapFile .= ' ' . var_export($class, true) . ' => ' . $code; + } + $classmapFile .= "];\n"; + $runtimePath = $app->getRuntimePath(); + if (!is_dir($runtimePath)) { + @mkdir($runtimePath, 0755, true); + } + + file_put_contents($runtimePath . 'classmap.php', $classmapFile); + + $output->writeln('Succeed!'); + } + + protected function addClassMapCode($dir, $namespace, $classMap) + { + foreach ($this->createMap($dir, $namespace) as $class => $path) { + + $pathCode = $this->getPathCode($path) . ",\n"; + + if (!isset($classMap[$class])) { + $classMap[$class] = $pathCode; + } elseif ($classMap[$class] !== $pathCode && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($classMap[$class] . ' ' . $path, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . str_replace(["',\n"], [ + '', + ], $classMap[$class]) . '" and "' . $path . '", the first will be used.' + ); + } + } + return $classMap; + } + + protected function getPathCode($path) + { + $baseDir = ''; + $app = Container::get('app'); + $appPath = $this->normalizePath(realpath($app->getAppPath())); + $libPath = $this->normalizePath(realpath($app->getThinkPath() . 'library')); + $extendPath = $this->normalizePath(realpath($app->getRootPath() . 'extend')); + $path = $this->normalizePath($path); + + if (strpos($path, $libPath . '/') === 0) { + $path = substr($path, strlen($app->getThinkPath() . 'library')); + $baseDir = "'" . $libPath . "/'"; + } elseif (strpos($path, $appPath . '/') === 0) { + $path = substr($path, strlen($appPath) + 1); + $baseDir = "'" . $appPath . "/'"; + } elseif (strpos($path, $extendPath . '/') === 0) { + $path = substr($path, strlen($extendPath) + 1); + $baseDir = "'" . $extendPath . "/'"; + } + + if (false !== $path) { + $baseDir .= " . "; + } + + return $baseDir . ((false !== $path) ? var_export($path, true) : ""); + } + + protected function normalizePath($path) + { + $parts = []; + $path = strtr($path, '\\', '/'); + $prefix = ''; + $absolute = false; + + if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) { + $prefix = $match[1]; + $path = substr($path, strlen($prefix)); + } + + if (substr($path, 0, 1) === '/') { + $absolute = true; + $path = substr($path, 1); + } + + $up = false; + foreach (explode('/', $path) as $chunk) { + if ('..' === $chunk && ($absolute || $up)) { + array_pop($parts); + $up = !(empty($parts) || '..' === end($parts)); + } elseif ('.' !== $chunk && '' !== $chunk) { + $parts[] = $chunk; + $up = '..' !== $chunk; + } + } + + return $prefix . ($absolute ? '/' : '') . implode('/', $parts); + } + + protected function createMap($path, $namespace = null) + { + if (is_string($path)) { + if (is_file($path)) { + $path = [new \SplFileInfo($path)]; + } elseif (is_dir($path)) { + + $objects = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path), \RecursiveIteratorIterator::SELF_FIRST); + + $path = []; + + /** @var \SplFileInfo $object */ + foreach ($objects as $object) { + if ($object->isFile() && $object->getExtension() == 'php') { + $path[] = $object; + } + } + } else { + throw new \RuntimeException( + 'Could not scan for classes inside "' . $path . + '" which does not appear to be a file nor a folder' + ); + } + } + + $map = []; + + /** @var \SplFileInfo $file */ + foreach ($path as $file) { + $filePath = $file->getRealPath(); + + if (pathinfo($filePath, PATHINFO_EXTENSION) != 'php') { + continue; + } + + $classes = $this->findClasses($filePath); + + foreach ($classes as $class) { + if (null !== $namespace && 0 !== strpos($class, $namespace)) { + continue; + } + + if (!isset($map[$class])) { + $map[$class] = $filePath; + } elseif ($map[$class] !== $filePath && !preg_match('{/(test|fixture|example|stub)s?/}i', strtr($map[$class] . ' ' . $filePath, '\\', '/'))) { + $this->output->writeln( + 'Warning: Ambiguous class resolution, "' . $class . '"' . + ' was found in both "' . $map[$class] . '" and "' . $filePath . '", the first will be used.' + ); + } + } + } + + return $map; + } + + protected function findClasses($path) + { + $extraTypes = '|trait'; + + $contents = @php_strip_whitespace($path); + if (!$contents) { + if (!file_exists($path)) { + $message = 'File at "%s" does not exist, check your classmap definitions'; + } elseif (!is_readable($path)) { + $message = 'File at "%s" is not readable, check its permissions'; + } elseif ('' === trim(file_get_contents($path))) { + return []; + } else { + $message = 'File at "%s" could not be parsed as PHP, it may be binary or corrupted'; + } + $error = error_get_last(); + if (isset($error['message'])) { + $message .= PHP_EOL . 'The following message may be helpful:' . PHP_EOL . $error['message']; + } + throw new \RuntimeException(sprintf($message, $path)); + } + + if (!preg_match('{\b(?:class|interface' . $extraTypes . ')\s}i', $contents)) { + return []; + } + + // strip heredocs/nowdocs + $contents = preg_replace('{<<<\s*(\'?)(\w+)\\1(?:\r\n|\n|\r)(?:.*?)(?:\r\n|\n|\r)\\2(?=\r\n|\n|\r|;)}s', 'null', $contents); + // strip strings + $contents = preg_replace('{"[^"\\\\]*+(\\\\.[^"\\\\]*+)*+"|\'[^\'\\\\]*+(\\\\.[^\'\\\\]*+)*+\'}s', 'null', $contents); + // strip leading non-php code if needed + if (substr($contents, 0, 2) !== '.+<\?}s', '?>'); + if (false !== $pos && false === strpos(substr($contents, $pos), '])(?Pclass|interface' . $extraTypes . ') \s++ (?P[a-zA-Z_\x7f-\xff:][a-zA-Z0-9_\x7f-\xff:\-]*+) + | \b(?])(?Pnamespace) (?P\s++[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\s*+\\\\\s*+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)*+)? \s*+ [\{;] + ) + }ix', $contents, $matches); + + $classes = []; + $namespace = ''; + + for ($i = 0, $len = count($matches['type']); $i < $len; $i++) { + if (!empty($matches['ns'][$i])) { + $namespace = str_replace([' ', "\t", "\r", "\n"], '', $matches['nsname'][$i]) . '\\'; + } else { + $name = $matches['name'][$i]; + if (':' === $name[0]) { + $name = 'xhp' . substr(str_replace(['-', ':'], ['_', '__'], $name), 1); + } elseif ('enum' === $matches['type'][$i]) { + $name = rtrim($name, ':'); + } + $classes[] = ltrim($namespace . $name, '\\'); + } + } + + return $classes; + } + +} diff --git a/thinkphp/library/think/console/command/optimize/Config.php b/thinkphp/library/think/console/command/optimize/Config.php new file mode 100644 index 0000000..da95556 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Config.php @@ -0,0 +1,107 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Argument; +use think\console\Output; +use think\Container; +use think\facade\App; + +class Config extends Command +{ + protected function configure() + { + $this->setName('optimize:config') + ->addArgument('module', Argument::OPTIONAL, 'Build module config cache .') + ->setDescription('Build config and common file cache.'); + } + + protected function execute(Input $input, Output $output) + { + if ($input->getArgument('module')) { + $module = $input->getArgument('module') . DIRECTORY_SEPARATOR; + } else { + $module = ''; + } + + $content = 'buildCacheContent($module); + $runtimePath = App::getRuntimePath(); + if (!is_dir($runtimePath . $module)) { + @mkdir($runtimePath . $module, 0755, true); + } + + file_put_contents($runtimePath . $module . 'init.php', $content); + + $output->writeln('Succeed!'); + } + + protected function buildCacheContent($module) + { + $content = '// This cache file is automatically generated at:' . date('Y-m-d H:i:s') . PHP_EOL; + $path = realpath(App::getAppPath() . $module) . DIRECTORY_SEPARATOR; + if ($module) { + $configPath = is_dir($path . 'config') ? $path . 'config' : App::getConfigPath() . $module; + } else { + $configPath = App::getConfigPath(); + } + $ext = App::getConfigExt(); + $config = Container::get('config'); + + $files = is_dir($configPath) ? scandir($configPath) : []; + + foreach ($files as $file) { + if ('.' . pathinfo($file, PATHINFO_EXTENSION) === $ext) { + $filename = $configPath . DIRECTORY_SEPARATOR . $file; + $config->load($filename, pathinfo($file, PATHINFO_FILENAME)); + } + } + + // 加载行为扩展文件 + if (is_file($path . 'tags.php')) { + $tags = include $path . 'tags.php'; + if (is_array($tags)) { + $content .= PHP_EOL . '\think\facade\Hook::import(' . (var_export($tags, true)) . ');' . PHP_EOL; + } + } + + // 加载公共文件 + if (is_file($path . 'common.php')) { + $common = substr(php_strip_whitespace($path . 'common.php'), 6); + if ($common) { + $content .= PHP_EOL . $common . PHP_EOL; + } + } + + if ('' == $module) { + $content .= PHP_EOL . substr(php_strip_whitespace(App::getThinkPath() . 'helper.php'), 6) . PHP_EOL; + + if (is_file($path . 'middleware.php')) { + $middleware = include $path . 'middleware.php'; + if (is_array($middleware)) { + $content .= PHP_EOL . '\think\Container::get("middleware")->import(' . var_export($middleware, true) . ');' . PHP_EOL; + } + } + } + + if (is_file($path . 'provider.php')) { + $provider = include $path . 'provider.php'; + if (is_array($provider)) { + $content .= PHP_EOL . '\think\Container::getInstance()->bindTo(' . var_export($provider, true) . ');' . PHP_EOL; + } + } + + $content .= PHP_EOL . '\think\facade\Config::set(' . var_export($config->get(), true) . ');' . PHP_EOL; + + return $content; + } +} diff --git a/thinkphp/library/think/console/command/optimize/Route.php b/thinkphp/library/think/console/command/optimize/Route.php new file mode 100644 index 0000000..f6dc632 --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Route.php @@ -0,0 +1,66 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\Output; +use think\Container; + +class Route extends Command +{ + protected function configure() + { + $this->setName('optimize:route') + ->setDescription('Build route cache.'); + } + + protected function execute(Input $input, Output $output) + { + $filename = Container::get('app')->getRuntimePath() . 'route.php'; + if (is_file($filename)) { + unlink($filename); + } + file_put_contents($filename, $this->buildRouteCache()); + $output->writeln('Succeed!'); + } + + protected function buildRouteCache() + { + Container::get('route')->setName([]); + Container::get('route')->setTestMode(true); + // 路由检测 + $path = Container::get('app')->getRoutePath(); + + $files = is_dir($path) ? scandir($path) : []; + + foreach ($files as $file) { + if (strpos($file, '.php')) { + $filename = $path . DIRECTORY_SEPARATOR . $file; + // 导入路由配置 + $rules = include $filename; + if (is_array($rules)) { + Container::get('route')->import($rules); + } + } + } + + if (Container::get('config')->get('route_annotation')) { + $suffix = Container::get('config')->get('controller_suffix') || Container::get('config')->get('class_suffix'); + include Container::get('build')->buildRoute($suffix); + } + + $content = 'getName(), true) . ';'; + return $content; + } + +} diff --git a/thinkphp/library/think/console/command/optimize/Schema.php b/thinkphp/library/think/console/command/optimize/Schema.php new file mode 100644 index 0000000..16ac83d --- /dev/null +++ b/thinkphp/library/think/console/command/optimize/Schema.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- +namespace think\console\command\optimize; + +use think\console\Command; +use think\console\Input; +use think\console\input\Option; +use think\console\Output; +use think\Db; +use think\facade\App; + +class Schema extends Command +{ + protected function configure() + { + $this->setName('optimize:schema') + ->addOption('db', null, Option::VALUE_REQUIRED, 'db name .') + ->addOption('table', null, Option::VALUE_REQUIRED, 'table name .') + ->addOption('module', null, Option::VALUE_REQUIRED, 'module name .') + ->setDescription('Build database schema cache.'); + } + + protected function execute(Input $input, Output $output) + { + if (!is_dir(App::getRuntimePath() . 'schema')) { + @mkdir(App::getRuntimePath() . 'schema', 0755, true); + } + + if ($input->hasOption('module')) { + $module = $input->getOption('module'); + // 读取模型 + $path = App::getAppPath() . $module . DIRECTORY_SEPARATOR . 'model'; + $list = is_dir($path) ? scandir($path) : []; + $namespace = App::getNamespace(); + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\' . $module . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + + $output->writeln('Succeed!'); + return; + } elseif ($input->hasOption('table')) { + $table = $input->getOption('table'); + if (false === strpos($table, '.')) { + $dbName = Db::getConfig('database'); + } + + $tables[] = $table; + } elseif ($input->hasOption('db')) { + $dbName = $input->getOption('db'); + $tables = Db::getConnection()->getTables($dbName); + } elseif (!\think\facade\Config::get('app_multi_module')) { + $namespace = App::getNamespace(); + $path = App::getAppPath() . 'model'; + $list = is_dir($path) ? scandir($path) : []; + + foreach ($list as $file) { + if (0 === strpos($file, '.')) { + continue; + } + $class = '\\' . $namespace . '\\model\\' . pathinfo($file, PATHINFO_FILENAME); + $this->buildModelSchema($class); + } + + $output->writeln('Succeed!'); + return; + } else { + $tables = Db::getConnection()->getTables(); + } + + $db = isset($dbName) ? $dbName . '.' : ''; + $this->buildDataBaseSchema($tables, $db); + + $output->writeln('Succeed!'); + } + + protected function buildModelSchema($class) + { + $reflect = new \ReflectionClass($class); + if (!$reflect->isAbstract() && $reflect->isSubclassOf('\think\Model')) { + $table = $class::getTable(); + $dbName = $class::getConfig('database'); + $content = 'getFields($table); + $content .= var_export($info, true) . ';'; + + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . '.' . $table . '.php', $content); + } + } + + protected function buildDataBaseSchema($tables, $db) + { + if ('' == $db) { + $dbName = Db::getConfig('database') . '.'; + } else { + $dbName = $db; + } + + foreach ($tables as $table) { + $content = 'getFields($db . $table); + $content .= var_export($info, true) . ';'; + file_put_contents(App::getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $dbName . $table . '.php', $content); + } + } +} diff --git a/thinkphp/library/think/console/input/Argument.php b/thinkphp/library/think/console/input/Argument.php new file mode 100644 index 0000000..16223bb --- /dev/null +++ b/thinkphp/library/think/console/input/Argument.php @@ -0,0 +1,115 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Argument +{ + + const REQUIRED = 1; + const OPTIONAL = 2; + const IS_ARRAY = 4; + + private $name; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 参数名 + * @param int $mode 参数类型: self::REQUIRED 或者 self::OPTIONAL + * @param string $description 描述 + * @param mixed $default 默认值 (仅 self::OPTIONAL 类型有效) + * @throws \InvalidArgumentException + */ + public function __construct($name, $mode = null, $description = '', $default = null) + { + if (null === $mode) { + $mode = self::OPTIONAL; + } elseif (!is_int($mode) || $mode > 7 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Argument mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->mode = $mode; + $this->description = $description; + + $this->setDefault($default); + } + + /** + * 获取参数名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否必须 + * @return bool + */ + public function isRequired() + { + return self::REQUIRED === (self::REQUIRED & $this->mode); + } + + /** + * 该参数是否接受数组 + * @return bool + */ + public function isArray() + { + return self::IS_ARRAY === (self::IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::REQUIRED === $this->mode && null !== $default) { + throw new \LogicException('Cannot set a default value except for InputArgument::OPTIONAL mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array argument must be an array.'); + } + } + + $this->default = $default; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述 + * @return string + */ + public function getDescription() + { + return $this->description; + } +} diff --git a/thinkphp/library/think/console/input/Definition.php b/thinkphp/library/think/console/input/Definition.php new file mode 100644 index 0000000..c71977e --- /dev/null +++ b/thinkphp/library/think/console/input/Definition.php @@ -0,0 +1,375 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Definition +{ + + /** + * @var Argument[] + */ + private $arguments; + + private $requiredCount; + private $hasAnArrayArgument = false; + private $hasOptional; + + /** + * @var Option[] + */ + private $options; + private $shortcuts; + + /** + * 构造方法 + * @param array $definition + * @api + */ + public function __construct(array $definition = []) + { + $this->setDefinition($definition); + } + + /** + * 设置指令的定义 + * @param array $definition 定义的数组 + */ + public function setDefinition(array $definition) + { + $arguments = []; + $options = []; + foreach ($definition as $item) { + if ($item instanceof Option) { + $options[] = $item; + } else { + $arguments[] = $item; + } + } + + $this->setArguments($arguments); + $this->setOptions($options); + } + + /** + * 设置参数 + * @param Argument[] $arguments 参数数组 + */ + public function setArguments($arguments = []) + { + $this->arguments = []; + $this->requiredCount = 0; + $this->hasOptional = false; + $this->hasAnArrayArgument = false; + $this->addArguments($arguments); + } + + /** + * 添加参数 + * @param Argument[] $arguments 参数数组 + * @api + */ + public function addArguments($arguments = []) + { + if (null !== $arguments) { + foreach ($arguments as $argument) { + $this->addArgument($argument); + } + } + } + + /** + * 添加一个参数 + * @param Argument $argument 参数 + * @throws \LogicException + */ + public function addArgument(Argument $argument) + { + if (isset($this->arguments[$argument->getName()])) { + throw new \LogicException(sprintf('An argument with name "%s" already exists.', $argument->getName())); + } + + if ($this->hasAnArrayArgument) { + throw new \LogicException('Cannot add an argument after an array argument.'); + } + + if ($argument->isRequired() && $this->hasOptional) { + throw new \LogicException('Cannot add a required argument after an optional one.'); + } + + if ($argument->isArray()) { + $this->hasAnArrayArgument = true; + } + + if ($argument->isRequired()) { + ++$this->requiredCount; + } else { + $this->hasOptional = true; + } + + $this->arguments[$argument->getName()] = $argument; + } + + /** + * 根据名称或者位置获取参数 + * @param string|int $name 参数名或者位置 + * @return Argument 参数 + * @throws \InvalidArgumentException + */ + public function getArgument($name) + { + if (!$this->hasArgument($name)) { + throw new \InvalidArgumentException(sprintf('The "%s" argument does not exist.', $name)); + } + + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return $arguments[$name]; + } + + /** + * 根据名称或位置检查是否具有某个参数 + * @param string|int $name 参数名或者位置 + * @return bool + * @api + */ + public function hasArgument($name) + { + $arguments = is_int($name) ? array_values($this->arguments) : $this->arguments; + + return isset($arguments[$name]); + } + + /** + * 获取所有的参数 + * @return Argument[] 参数数组 + */ + public function getArguments() + { + return $this->arguments; + } + + /** + * 获取参数数量 + * @return int + */ + public function getArgumentCount() + { + return $this->hasAnArrayArgument ? PHP_INT_MAX : count($this->arguments); + } + + /** + * 获取必填的参数的数量 + * @return int + */ + public function getArgumentRequiredCount() + { + return $this->requiredCount; + } + + /** + * 获取参数默认值 + * @return array + */ + public function getArgumentDefaults() + { + $values = []; + foreach ($this->arguments as $argument) { + $values[$argument->getName()] = $argument->getDefault(); + } + + return $values; + } + + /** + * 设置选项 + * @param Option[] $options 选项数组 + */ + public function setOptions($options = []) + { + $this->options = []; + $this->shortcuts = []; + $this->addOptions($options); + } + + /** + * 添加选项 + * @param Option[] $options 选项数组 + * @api + */ + public function addOptions($options = []) + { + foreach ($options as $option) { + $this->addOption($option); + } + } + + /** + * 添加一个选项 + * @param Option $option 选项 + * @throws \LogicException + * @api + */ + public function addOption(Option $option) + { + if (isset($this->options[$option->getName()]) && !$option->equals($this->options[$option->getName()])) { + throw new \LogicException(sprintf('An option named "%s" already exists.', $option->getName())); + } + + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + if (isset($this->shortcuts[$shortcut]) + && !$option->equals($this->options[$this->shortcuts[$shortcut]]) + ) { + throw new \LogicException(sprintf('An option with shortcut "%s" already exists.', $shortcut)); + } + } + } + + $this->options[$option->getName()] = $option; + if ($option->getShortcut()) { + foreach (explode('|', $option->getShortcut()) as $shortcut) { + $this->shortcuts[$shortcut] = $option->getName(); + } + } + } + + /** + * 根据名称获取选项 + * @param string $name 选项名 + * @return Option + * @throws \InvalidArgumentException + * @api + */ + public function getOption($name) + { + if (!$this->hasOption($name)) { + throw new \InvalidArgumentException(sprintf('The "--%s" option does not exist.', $name)); + } + + return $this->options[$name]; + } + + /** + * 根据名称检查是否有这个选项 + * @param string $name 选项名 + * @return bool + * @api + */ + public function hasOption($name) + { + return isset($this->options[$name]); + } + + /** + * 获取所有选项 + * @return Option[] + * @api + */ + public function getOptions() + { + return $this->options; + } + + /** + * 根据名称检查某个选项是否有短名称 + * @param string $name 短名称 + * @return bool + */ + public function hasShortcut($name) + { + return isset($this->shortcuts[$name]); + } + + /** + * 根据短名称获取选项 + * @param string $shortcut 短名称 + * @return Option + */ + public function getOptionForShortcut($shortcut) + { + return $this->getOption($this->shortcutToName($shortcut)); + } + + /** + * 获取所有选项的默认值 + * @return array + */ + public function getOptionDefaults() + { + $values = []; + foreach ($this->options as $option) { + $values[$option->getName()] = $option->getDefault(); + } + + return $values; + } + + /** + * 根据短名称获取选项名 + * @param string $shortcut 短名称 + * @return string + * @throws \InvalidArgumentException + */ + private function shortcutToName($shortcut) + { + if (!isset($this->shortcuts[$shortcut])) { + throw new \InvalidArgumentException(sprintf('The "-%s" option does not exist.', $shortcut)); + } + + return $this->shortcuts[$shortcut]; + } + + /** + * 获取该指令的介绍 + * @param bool $short 是否简洁介绍 + * @return string + */ + public function getSynopsis($short = false) + { + $elements = []; + + if ($short && $this->getOptions()) { + $elements[] = '[options]'; + } elseif (!$short) { + foreach ($this->getOptions() as $option) { + $value = ''; + if ($option->acceptValue()) { + $value = sprintf(' %s%s%s', $option->isValueOptional() ? '[' : '', strtoupper($option->getName()), $option->isValueOptional() ? ']' : ''); + } + + $shortcut = $option->getShortcut() ? sprintf('-%s|', $option->getShortcut()) : ''; + $elements[] = sprintf('[%s--%s%s]', $shortcut, $option->getName(), $value); + } + } + + if (count($elements) && $this->getArguments()) { + $elements[] = '[--]'; + } + + foreach ($this->getArguments() as $argument) { + $element = '<' . $argument->getName() . '>'; + if (!$argument->isRequired()) { + $element = '[' . $element . ']'; + } elseif ($argument->isArray()) { + $element .= ' (' . $element . ')'; + } + + if ($argument->isArray()) { + $element .= '...'; + } + + $elements[] = $element; + } + + return implode(' ', $elements); + } +} diff --git a/thinkphp/library/think/console/input/Option.php b/thinkphp/library/think/console/input/Option.php new file mode 100644 index 0000000..e5707c9 --- /dev/null +++ b/thinkphp/library/think/console/input/Option.php @@ -0,0 +1,190 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\input; + +class Option +{ + + const VALUE_NONE = 1; + const VALUE_REQUIRED = 2; + const VALUE_OPTIONAL = 4; + const VALUE_IS_ARRAY = 8; + + private $name; + private $shortcut; + private $mode; + private $default; + private $description; + + /** + * 构造方法 + * @param string $name 选项名 + * @param string|array $shortcut 短名称,多个用|隔开或者使用数组 + * @param int $mode 选项类型(可选类型为 self::VALUE_*) + * @param string $description 描述 + * @param mixed $default 默认值 (类型为 self::VALUE_REQUIRED 或者 self::VALUE_NONE 的时候必须为null) + * @throws \InvalidArgumentException + */ + public function __construct($name, $shortcut = null, $mode = null, $description = '', $default = null) + { + if (0 === strpos($name, '--')) { + $name = substr($name, 2); + } + + if (empty($name)) { + throw new \InvalidArgumentException('An option name cannot be empty.'); + } + + if (empty($shortcut)) { + $shortcut = null; + } + + if (null !== $shortcut) { + if (is_array($shortcut)) { + $shortcut = implode('|', $shortcut); + } + $shortcuts = preg_split('{(\|)-?}', ltrim($shortcut, '-')); + $shortcuts = array_filter($shortcuts); + $shortcut = implode('|', $shortcuts); + + if (empty($shortcut)) { + throw new \InvalidArgumentException('An option shortcut cannot be empty.'); + } + } + + if (null === $mode) { + $mode = self::VALUE_NONE; + } elseif (!is_int($mode) || $mode > 15 || $mode < 1) { + throw new \InvalidArgumentException(sprintf('Option mode "%s" is not valid.', $mode)); + } + + $this->name = $name; + $this->shortcut = $shortcut; + $this->mode = $mode; + $this->description = $description; + + if ($this->isArray() && !$this->acceptValue()) { + throw new \InvalidArgumentException('Impossible to have an option mode VALUE_IS_ARRAY if the option does not accept a value.'); + } + + $this->setDefault($default); + } + + /** + * 获取短名称 + * @return string + */ + public function getShortcut() + { + return $this->shortcut; + } + + /** + * 获取选项名 + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 是否可以设置值 + * @return bool 类型不是 self::VALUE_NONE 的时候返回true,其他均返回false + */ + public function acceptValue() + { + return $this->isValueRequired() || $this->isValueOptional(); + } + + /** + * 是否必须 + * @return bool 类型是 self::VALUE_REQUIRED 的时候返回true,其他均返回false + */ + public function isValueRequired() + { + return self::VALUE_REQUIRED === (self::VALUE_REQUIRED & $this->mode); + } + + /** + * 是否可选 + * @return bool 类型是 self::VALUE_OPTIONAL 的时候返回true,其他均返回false + */ + public function isValueOptional() + { + return self::VALUE_OPTIONAL === (self::VALUE_OPTIONAL & $this->mode); + } + + /** + * 选项值是否接受数组 + * @return bool 类型是 self::VALUE_IS_ARRAY 的时候返回true,其他均返回false + */ + public function isArray() + { + return self::VALUE_IS_ARRAY === (self::VALUE_IS_ARRAY & $this->mode); + } + + /** + * 设置默认值 + * @param mixed $default 默认值 + * @throws \LogicException + */ + public function setDefault($default = null) + { + if (self::VALUE_NONE === (self::VALUE_NONE & $this->mode) && null !== $default) { + throw new \LogicException('Cannot set a default value when using InputOption::VALUE_NONE mode.'); + } + + if ($this->isArray()) { + if (null === $default) { + $default = []; + } elseif (!is_array($default)) { + throw new \LogicException('A default value for an array option must be an array.'); + } + } + + $this->default = $this->acceptValue() ? $default : false; + } + + /** + * 获取默认值 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 获取描述文字 + * @return string + */ + public function getDescription() + { + return $this->description; + } + + /** + * 检查所给选项是否是当前这个 + * @param Option $option + * @return bool + */ + public function equals(Option $option) + { + return $option->getName() === $this->getName() + && $option->getShortcut() === $this->getShortcut() + && $option->getDefault() === $this->getDefault() + && $option->isArray() === $this->isArray() + && $option->isValueRequired() === $this->isValueRequired() + && $option->isValueOptional() === $this->isValueOptional(); + } +} diff --git a/thinkphp/library/think/console/output/Ask.php b/thinkphp/library/think/console/output/Ask.php new file mode 100644 index 0000000..3933eb2 --- /dev/null +++ b/thinkphp/library/think/console/output/Ask.php @@ -0,0 +1,340 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\console\Input; +use think\console\Output; +use think\console\output\question\Choice; +use think\console\output\question\Confirmation; + +class Ask +{ + private static $stty; + + private static $shell; + + /** @var Input */ + protected $input; + + /** @var Output */ + protected $output; + + /** @var Question */ + protected $question; + + public function __construct(Input $input, Output $output, Question $question) + { + $this->input = $input; + $this->output = $output; + $this->question = $question; + } + + public function run() + { + if (!$this->input->isInteractive()) { + return $this->question->getDefault(); + } + + if (!$this->question->getValidator()) { + return $this->doAsk(); + } + + $that = $this; + + $interviewer = function () use ($that) { + return $that->doAsk(); + }; + + return $this->validateAttempts($interviewer); + } + + protected function doAsk() + { + $this->writePrompt(); + + $inputStream = STDIN; + $autocomplete = $this->question->getAutocompleterValues(); + + if (null === $autocomplete || !$this->hasSttyAvailable()) { + $ret = false; + if ($this->question->isHidden()) { + try { + $ret = trim($this->getHiddenResponse($inputStream)); + } catch (\RuntimeException $e) { + if (!$this->question->isHiddenFallback()) { + throw $e; + } + } + } + + if (false === $ret) { + $ret = fgets($inputStream, 4096); + if (false === $ret) { + throw new \RuntimeException('Aborted'); + } + $ret = trim($ret); + } + } else { + $ret = trim($this->autocomplete($inputStream)); + } + + $ret = strlen($ret) > 0 ? $ret : $this->question->getDefault(); + + if ($normalizer = $this->question->getNormalizer()) { + return $normalizer($ret); + } + + return $ret; + } + + private function autocomplete($inputStream) + { + $autocomplete = $this->question->getAutocompleterValues(); + $ret = ''; + + $i = 0; + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -icanon -echo'); + + while (!feof($inputStream)) { + $c = fread($inputStream, 1); + + if ("\177" === $c) { + if (0 === $numMatches && 0 !== $i) { + --$i; + $this->output->write("\033[1D"); + } + + if ($i === 0) { + $ofs = -1; + $matches = $autocomplete; + $numMatches = count($matches); + } else { + $numMatches = 0; + } + + $ret = substr($ret, 0, $i); + } elseif ("\033" === $c) { + $c .= fread($inputStream, 2); + + if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) { + if ('A' === $c[2] && -1 === $ofs) { + $ofs = 0; + } + + if (0 === $numMatches) { + continue; + } + + $ofs += ('A' === $c[2]) ? -1 : 1; + $ofs = ($numMatches + $ofs) % $numMatches; + } + } elseif (ord($c) < 32) { + if ("\t" === $c || "\n" === $c) { + if ($numMatches > 0 && -1 !== $ofs) { + $ret = $matches[$ofs]; + $this->output->write(substr($ret, $i)); + $i = strlen($ret); + } + + if ("\n" === $c) { + $this->output->write($c); + break; + } + + $numMatches = 0; + } + + continue; + } else { + $this->output->write($c); + $ret .= $c; + ++$i; + + $numMatches = 0; + $ofs = 0; + + foreach ($autocomplete as $value) { + if (0 === strpos($value, $ret) && $i !== strlen($value)) { + $matches[$numMatches++] = $value; + } + } + } + + $this->output->write("\033[K"); + + if ($numMatches > 0 && -1 !== $ofs) { + $this->output->write("\0337"); + $this->output->highlight(substr($matches[$ofs], $i)); + $this->output->write("\0338"); + } + } + + shell_exec(sprintf('stty %s', $sttyMode)); + + return $ret; + } + + protected function getHiddenResponse($inputStream) + { + if ('\\' === DIRECTORY_SEPARATOR) { + $exe = __DIR__ . '/../bin/hiddeninput.exe'; + + $value = rtrim(shell_exec($exe)); + $this->output->writeln(''); + + if (isset($tmpExe)) { + unlink($tmpExe); + } + + return $value; + } + + if ($this->hasSttyAvailable()) { + $sttyMode = shell_exec('stty -g'); + + shell_exec('stty -echo'); + $value = fgets($inputStream, 4096); + shell_exec(sprintf('stty %s', $sttyMode)); + + if (false === $value) { + throw new \RuntimeException('Aborted'); + } + + $value = trim($value); + $this->output->writeln(''); + + return $value; + } + + if (false !== $shell = $this->getShell()) { + $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword'; + $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd); + $value = rtrim(shell_exec($command)); + $this->output->writeln(''); + + return $value; + } + + throw new \RuntimeException('Unable to hide the response.'); + } + + protected function validateAttempts($interviewer) + { + /** @var \Exception $error */ + $error = null; + $attempts = $this->question->getMaxAttempts(); + while (null === $attempts || $attempts--) { + if (null !== $error) { + $this->output->error($error->getMessage()); + } + + try { + return call_user_func($this->question->getValidator(), $interviewer()); + } catch (\Exception $error) { + } + } + + throw $error; + } + + /** + * 显示问题的提示信息 + */ + protected function writePrompt() + { + $text = $this->question->getQuestion(); + $default = $this->question->getDefault(); + + switch (true) { + case null === $default: + $text = sprintf(' %s:', $text); + + break; + + case $this->question instanceof Confirmation: + $text = sprintf(' %s (yes/no) [%s]:', $text, $default ? 'yes' : 'no'); + + break; + + case $this->question instanceof Choice && $this->question->isMultiselect(): + $choices = $this->question->getChoices(); + $default = explode(',', $default); + + foreach ($default as $key => $value) { + $default[$key] = $choices[trim($value)]; + } + + $text = sprintf(' %s [%s]:', $text, implode(', ', $default)); + + break; + + case $this->question instanceof Choice: + $choices = $this->question->getChoices(); + $text = sprintf(' %s [%s]:', $text, $choices[$default]); + + break; + + default: + $text = sprintf(' %s [%s]:', $text, $default); + } + + $this->output->writeln($text); + + if ($this->question instanceof Choice) { + $width = max(array_map('strlen', array_keys($this->question->getChoices()))); + + foreach ($this->question->getChoices() as $key => $value) { + $this->output->writeln(sprintf(" [%-${width}s] %s", $key, $value)); + } + } + + $this->output->write(' > '); + } + + private function getShell() + { + if (null !== self::$shell) { + return self::$shell; + } + + self::$shell = false; + + if (file_exists('/usr/bin/env')) { + $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null"; + foreach (['bash', 'zsh', 'ksh', 'csh'] as $sh) { + if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) { + self::$shell = $sh; + break; + } + } + } + + return self::$shell; + } + + private function hasSttyAvailable() + { + if (null !== self::$stty) { + return self::$stty; + } + + exec('stty 2>&1', $output, $exitcode); + + return self::$stty = $exitcode === 0; + } +} diff --git a/thinkphp/library/think/console/output/Descriptor.php b/thinkphp/library/think/console/output/Descriptor.php new file mode 100644 index 0000000..6d98d53 --- /dev/null +++ b/thinkphp/library/think/console/output/Descriptor.php @@ -0,0 +1,319 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +use think\Console; +use think\console\Command; +use think\console\input\Argument as InputArgument; +use think\console\input\Definition as InputDefinition; +use think\console\input\Option as InputOption; +use think\console\Output; +use think\console\output\descriptor\Console as ConsoleDescription; + +class Descriptor +{ + + /** + * @var Output + */ + protected $output; + + /** + * {@inheritdoc} + */ + public function describe(Output $output, $object, array $options = []) + { + $this->output = $output; + + switch (true) { + case $object instanceof InputArgument: + $this->describeInputArgument($object, $options); + break; + case $object instanceof InputOption: + $this->describeInputOption($object, $options); + break; + case $object instanceof InputDefinition: + $this->describeInputDefinition($object, $options); + break; + case $object instanceof Command: + $this->describeCommand($object, $options); + break; + case $object instanceof Console: + $this->describeConsole($object, $options); + break; + default: + throw new \InvalidArgumentException(sprintf('Object of type "%s" is not describable.', get_class($object))); + } + } + + /** + * 输出内容 + * @param string $content + * @param bool $decorated + */ + protected function write($content, $decorated = false) + { + $this->output->write($content, false, $decorated ? Output::OUTPUT_NORMAL : Output::OUTPUT_RAW); + } + + /** + * 描述参数 + * @param InputArgument $argument + * @param array $options + * @return string|mixed + */ + protected function describeInputArgument(InputArgument $argument, array $options = []) + { + if (null !== $argument->getDefault() + && (!is_array($argument->getDefault()) + || count($argument->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($argument->getDefault())); + } else { + $default = ''; + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : strlen($argument->getName()); + $spacingWidth = $totalWidth - strlen($argument->getName()) + 2; + + $this->writeText(sprintf(" %s%s%s%s", $argument->getName(), str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', PHP_EOL . str_repeat(' ', $totalWidth + 17), $argument->getDescription()), $default), $options); + } + + /** + * 描述选项 + * @param InputOption $option + * @param array $options + * @return string|mixed + */ + protected function describeInputOption(InputOption $option, array $options = []) + { + if ($option->acceptValue() && null !== $option->getDefault() + && (!is_array($option->getDefault()) + || count($option->getDefault())) + ) { + $default = sprintf(' [default: %s]', $this->formatDefaultValue($option->getDefault())); + } else { + $default = ''; + } + + $value = ''; + if ($option->acceptValue()) { + $value = '=' . strtoupper($option->getName()); + + if ($option->isValueOptional()) { + $value = '[' . $value . ']'; + } + } + + $totalWidth = isset($options['total_width']) ? $options['total_width'] : $this->calculateTotalWidthForOptions([$option]); + $synopsis = sprintf('%s%s', $option->getShortcut() ? sprintf('-%s, ', $option->getShortcut()) : ' ', sprintf('--%s%s', $option->getName(), $value)); + + $spacingWidth = $totalWidth - strlen($synopsis) + 2; + + $this->writeText(sprintf(" %s%s%s%s%s", $synopsis, str_repeat(' ', $spacingWidth), // + 17 = 2 spaces + + + 2 spaces + preg_replace('/\s*\R\s*/', "\n" . str_repeat(' ', $totalWidth + 17), $option->getDescription()), $default, $option->isArray() ? ' (multiple values allowed)' : ''), $options); + } + + /** + * 描述输入 + * @param InputDefinition $definition + * @param array $options + * @return string|mixed + */ + protected function describeInputDefinition(InputDefinition $definition, array $options = []) + { + $totalWidth = $this->calculateTotalWidthForOptions($definition->getOptions()); + foreach ($definition->getArguments() as $argument) { + $totalWidth = max($totalWidth, strlen($argument->getName())); + } + + if ($definition->getArguments()) { + $this->writeText('Arguments:', $options); + $this->writeText("\n"); + foreach ($definition->getArguments() as $argument) { + $this->describeInputArgument($argument, array_merge($options, ['total_width' => $totalWidth])); + $this->writeText("\n"); + } + } + + if ($definition->getArguments() && $definition->getOptions()) { + $this->writeText("\n"); + } + + if ($definition->getOptions()) { + $laterOptions = []; + + $this->writeText('Options:', $options); + foreach ($definition->getOptions() as $option) { + if (strlen($option->getShortcut()) > 1) { + $laterOptions[] = $option; + continue; + } + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + foreach ($laterOptions as $option) { + $this->writeText("\n"); + $this->describeInputOption($option, array_merge($options, ['total_width' => $totalWidth])); + } + } + } + + /** + * 描述指令 + * @param Command $command + * @param array $options + * @return string|mixed + */ + protected function describeCommand(Command $command, array $options = []) + { + $command->getSynopsis(true); + $command->getSynopsis(false); + $command->mergeConsoleDefinition(false); + + $this->writeText('Usage:', $options); + foreach (array_merge([$command->getSynopsis(true)], $command->getAliases(), $command->getUsages()) as $usage) { + $this->writeText("\n"); + $this->writeText(' ' . $usage, $options); + } + $this->writeText("\n"); + + $definition = $command->getNativeDefinition(); + if ($definition->getOptions() || $definition->getArguments()) { + $this->writeText("\n"); + $this->describeInputDefinition($definition, $options); + $this->writeText("\n"); + } + + if ($help = $command->getProcessedHelp()) { + $this->writeText("\n"); + $this->writeText('Help:', $options); + $this->writeText("\n"); + $this->writeText(' ' . str_replace("\n", "\n ", $help), $options); + $this->writeText("\n"); + } + } + + /** + * 描述控制台 + * @param Console $console + * @param array $options + * @return string|mixed + */ + protected function describeConsole(Console $console, array $options = []) + { + $describedNamespace = isset($options['namespace']) ? $options['namespace'] : null; + $description = new ConsoleDescription($console, $describedNamespace); + + if (isset($options['raw_text']) && $options['raw_text']) { + $width = $this->getColumnWidth($description->getCommands()); + + foreach ($description->getCommands() as $command) { + $this->writeText(sprintf("%-${width}s %s", $command->getName(), $command->getDescription()), $options); + $this->writeText("\n"); + } + } else { + if ('' != $help = $console->getHelp()) { + $this->writeText("$help\n\n", $options); + } + + $this->writeText("Usage:\n", $options); + $this->writeText(" command [options] [arguments]\n\n", $options); + + $this->describeInputDefinition(new InputDefinition($console->getDefinition()->getOptions()), $options); + + $this->writeText("\n"); + $this->writeText("\n"); + + $width = $this->getColumnWidth($description->getCommands()); + + if ($describedNamespace) { + $this->writeText(sprintf('Available commands for the "%s" namespace:', $describedNamespace), $options); + } else { + $this->writeText('Available commands:', $options); + } + + // add commands by namespace + foreach ($description->getNamespaces() as $namespace) { + if (!$describedNamespace && ConsoleDescription::GLOBAL_NAMESPACE !== $namespace['id']) { + $this->writeText("\n"); + $this->writeText(' ' . $namespace['id'] . '', $options); + } + + foreach ($namespace['commands'] as $name) { + $this->writeText("\n"); + $spacingWidth = $width - strlen($name); + $this->writeText(sprintf(" %s%s%s", $name, str_repeat(' ', $spacingWidth), $description->getCommand($name) + ->getDescription()), $options); + } + } + + $this->writeText("\n"); + } + } + + /** + * {@inheritdoc} + */ + private function writeText($content, array $options = []) + { + $this->write(isset($options['raw_text']) + && $options['raw_text'] ? strip_tags($content) : $content, isset($options['raw_output']) ? !$options['raw_output'] : true); + } + + /** + * 格式化 + * @param mixed $default + * @return string + */ + private function formatDefaultValue($default) + { + return json_encode($default, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE); + } + + /** + * @param Command[] $commands + * @return int + */ + private function getColumnWidth(array $commands) + { + $width = 0; + foreach ($commands as $command) { + $width = strlen($command->getName()) > $width ? strlen($command->getName()) : $width; + } + + return $width + 2; + } + + /** + * @param InputOption[] $options + * @return int + */ + private function calculateTotalWidthForOptions($options) + { + $totalWidth = 0; + foreach ($options as $option) { + $nameLength = 4 + strlen($option->getName()) + 2; // - + shortcut + , + whitespace + name + -- + + if ($option->acceptValue()) { + $valueLength = 1 + strlen($option->getName()); // = + value + $valueLength += $option->isValueOptional() ? 2 : 0; // [ + ] + + $nameLength += $valueLength; + } + $totalWidth = max($totalWidth, $nameLength); + } + + return $totalWidth; + } +} diff --git a/thinkphp/library/think/console/output/Formatter.php b/thinkphp/library/think/console/output/Formatter.php new file mode 100644 index 0000000..f8bee55 --- /dev/null +++ b/thinkphp/library/think/console/output/Formatter.php @@ -0,0 +1,198 @@ + +// +---------------------------------------------------------------------- +namespace think\console\output; + +use think\console\output\formatter\Stack as StyleStack; +use think\console\output\formatter\Style; + +class Formatter +{ + + private $decorated = false; + private $styles = []; + private $styleStack; + + /** + * 转义 + * @param string $text + * @return string + */ + public static function escape($text) + { + return preg_replace('/([^\\\\]?)setStyle('error', new Style('white', 'red')); + $this->setStyle('info', new Style('green')); + $this->setStyle('comment', new Style('yellow')); + $this->setStyle('question', new Style('black', 'cyan')); + $this->setStyle('highlight', new Style('red')); + $this->setStyle('warning', new Style('black', 'yellow')); + + $this->styleStack = new StyleStack(); + } + + /** + * 设置外观标识 + * @param bool $decorated 是否美化文字 + */ + public function setDecorated($decorated) + { + $this->decorated = (bool) $decorated; + } + + /** + * 获取外观标识 + * @return bool + */ + public function isDecorated() + { + return $this->decorated; + } + + /** + * 添加一个新样式 + * @param string $name 样式名 + * @param Style $style 样式实例 + */ + public function setStyle($name, Style $style) + { + $this->styles[strtolower($name)] = $style; + } + + /** + * 是否有这个样式 + * @param string $name + * @return bool + */ + public function hasStyle($name) + { + return isset($this->styles[strtolower($name)]); + } + + /** + * 获取样式 + * @param string $name + * @return Style + * @throws \InvalidArgumentException + */ + public function getStyle($name) + { + if (!$this->hasStyle($name)) { + throw new \InvalidArgumentException(sprintf('Undefined style: %s', $name)); + } + + return $this->styles[strtolower($name)]; + } + + /** + * 使用所给的样式格式化文字 + * @param string $message 文字 + * @return string + */ + public function format($message) + { + $offset = 0; + $output = ''; + $tagRegex = '[a-z][a-z0-9_=;-]*'; + preg_match_all("#<(($tagRegex) | /($tagRegex)?)>#isx", $message, $matches, PREG_OFFSET_CAPTURE); + foreach ($matches[0] as $i => $match) { + $pos = $match[1]; + $text = $match[0]; + + if (0 != $pos && '\\' == $message[$pos - 1]) { + continue; + } + + $output .= $this->applyCurrentStyle(substr($message, $offset, $pos - $offset)); + $offset = $pos + strlen($text); + + if ($open = '/' != $text[1]) { + $tag = $matches[1][$i][0]; + } else { + $tag = isset($matches[3][$i][0]) ? $matches[3][$i][0] : ''; + } + + if (!$open && !$tag) { + // + $this->styleStack->pop(); + } elseif (false === $style = $this->createStyleFromString(strtolower($tag))) { + $output .= $this->applyCurrentStyle($text); + } elseif ($open) { + $this->styleStack->push($style); + } else { + $this->styleStack->pop($style); + } + } + + $output .= $this->applyCurrentStyle(substr($message, $offset)); + + return str_replace('\\<', '<', $output); + } + + /** + * @return StyleStack + */ + public function getStyleStack() + { + return $this->styleStack; + } + + /** + * 根据字符串创建新的样式实例 + * @param string $string + * @return Style|bool + */ + private function createStyleFromString($string) + { + if (isset($this->styles[$string])) { + return $this->styles[$string]; + } + + if (!preg_match_all('/([^=]+)=([^;]+)(;|$)/', strtolower($string), $matches, PREG_SET_ORDER)) { + return false; + } + + $style = new Style(); + foreach ($matches as $match) { + array_shift($match); + + if ('fg' == $match[0]) { + $style->setForeground($match[1]); + } elseif ('bg' == $match[0]) { + $style->setBackground($match[1]); + } else { + try { + $style->setOption($match[1]); + } catch (\InvalidArgumentException $e) { + return false; + } + } + } + + return $style; + } + + /** + * 从堆栈应用样式到文字 + * @param string $text 文字 + * @return string + */ + private function applyCurrentStyle($text) + { + return $this->isDecorated() && strlen($text) > 0 ? $this->styleStack->getCurrent()->apply($text) : $text; + } +} diff --git a/thinkphp/library/think/console/output/Question.php b/thinkphp/library/think/console/output/Question.php new file mode 100644 index 0000000..03975f2 --- /dev/null +++ b/thinkphp/library/think/console/output/Question.php @@ -0,0 +1,211 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output; + +class Question +{ + + private $question; + private $attempts; + private $hidden = false; + private $hiddenFallback = true; + private $autocompleterValues; + private $validator; + private $default; + private $normalizer; + + /** + * 构造方法 + * @param string $question 问题 + * @param mixed $default 默认答案 + */ + public function __construct($question, $default = null) + { + $this->question = $question; + $this->default = $default; + } + + /** + * 获取问题 + * @return string + */ + public function getQuestion() + { + return $this->question; + } + + /** + * 获取默认答案 + * @return mixed + */ + public function getDefault() + { + return $this->default; + } + + /** + * 是否隐藏答案 + * @return bool + */ + public function isHidden() + { + return $this->hidden; + } + + /** + * 隐藏答案 + * @param bool $hidden + * @return Question + */ + public function setHidden($hidden) + { + if ($this->autocompleterValues) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->hidden = (bool) $hidden; + + return $this; + } + + /** + * 不能被隐藏是否撤销 + * @return bool + */ + public function isHiddenFallback() + { + return $this->hiddenFallback; + } + + /** + * 设置不能被隐藏的时候的操作 + * @param bool $fallback + * @return Question + */ + public function setHiddenFallback($fallback) + { + $this->hiddenFallback = (bool) $fallback; + + return $this; + } + + /** + * 获取自动完成 + * @return null|array|\Traversable + */ + public function getAutocompleterValues() + { + return $this->autocompleterValues; + } + + /** + * 设置自动完成的值 + * @param null|array|\Traversable $values + * @return Question + * @throws \InvalidArgumentException + * @throws \LogicException + */ + public function setAutocompleterValues($values) + { + if (is_array($values) && $this->isAssoc($values)) { + $values = array_merge(array_keys($values), array_values($values)); + } + + if (null !== $values && !is_array($values)) { + if (!$values instanceof \Traversable || $values instanceof \Countable) { + throw new \InvalidArgumentException('Autocompleter values can be either an array, `null` or an object implementing both `Countable` and `Traversable` interfaces.'); + } + } + + if ($this->hidden) { + throw new \LogicException('A hidden question cannot use the autocompleter.'); + } + + $this->autocompleterValues = $values; + + return $this; + } + + /** + * 设置答案的验证器 + * @param null|callable $validator + * @return Question The current instance + */ + public function setValidator($validator) + { + $this->validator = $validator; + + return $this; + } + + /** + * 获取验证器 + * @return null|callable + */ + public function getValidator() + { + return $this->validator; + } + + /** + * 设置最大重试次数 + * @param null|int $attempts + * @return Question + * @throws \InvalidArgumentException + */ + public function setMaxAttempts($attempts) + { + if (null !== $attempts && $attempts < 1) { + throw new \InvalidArgumentException('Maximum number of attempts must be a positive value.'); + } + + $this->attempts = $attempts; + + return $this; + } + + /** + * 获取最大重试次数 + * @return null|int + */ + public function getMaxAttempts() + { + return $this->attempts; + } + + /** + * 设置响应的回调 + * @param string|\Closure $normalizer + * @return Question + */ + public function setNormalizer($normalizer) + { + $this->normalizer = $normalizer; + + return $this; + } + + /** + * 获取响应回调 + * The normalizer can ba a callable (a string), a closure or a class implementing __invoke. + * @return string|\Closure + */ + public function getNormalizer() + { + return $this->normalizer; + } + + protected function isAssoc($array) + { + return (bool) count(array_filter(array_keys($array), 'is_string')); + } +} diff --git a/thinkphp/library/think/console/output/descriptor/Console.php b/thinkphp/library/think/console/output/descriptor/Console.php new file mode 100644 index 0000000..8739c53 --- /dev/null +++ b/thinkphp/library/think/console/output/descriptor/Console.php @@ -0,0 +1,153 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\descriptor; + +use think\Console as ThinkConsole; +use think\console\Command; + +class Console +{ + + const GLOBAL_NAMESPACE = '_global'; + + /** + * @var ThinkConsole + */ + private $console; + + /** + * @var null|string + */ + private $namespace; + + /** + * @var array + */ + private $namespaces; + + /** + * @var Command[] + */ + private $commands; + + /** + * @var Command[] + */ + private $aliases; + + /** + * 构造方法 + * @param ThinkConsole $console + * @param string|null $namespace + */ + public function __construct(ThinkConsole $console, $namespace = null) + { + $this->console = $console; + $this->namespace = $namespace; + } + + /** + * @return array + */ + public function getNamespaces() + { + if (null === $this->namespaces) { + $this->inspectConsole(); + } + + return $this->namespaces; + } + + /** + * @return Command[] + */ + public function getCommands() + { + if (null === $this->commands) { + $this->inspectConsole(); + } + + return $this->commands; + } + + /** + * @param string $name + * @return Command + * @throws \InvalidArgumentException + */ + public function getCommand($name) + { + if (!isset($this->commands[$name]) && !isset($this->aliases[$name])) { + throw new \InvalidArgumentException(sprintf('Command %s does not exist.', $name)); + } + + return isset($this->commands[$name]) ? $this->commands[$name] : $this->aliases[$name]; + } + + private function inspectConsole() + { + $this->commands = []; + $this->namespaces = []; + + $all = $this->console->all($this->namespace ? $this->console->findNamespace($this->namespace) : null); + foreach ($this->sortCommands($all) as $namespace => $commands) { + $names = []; + + /** @var Command $command */ + foreach ($commands as $name => $command) { + if (is_string($command)) { + $command = new $command(); + } + + if (!$command->getName()) { + continue; + } + + if ($command->getName() === $name) { + $this->commands[$name] = $command; + } else { + $this->aliases[$name] = $command; + } + + $names[] = $name; + } + + $this->namespaces[$namespace] = ['id' => $namespace, 'commands' => $names]; + } + } + + /** + * @param array $commands + * @return array + */ + private function sortCommands(array $commands) + { + $namespacedCommands = []; + foreach ($commands as $name => $command) { + $key = $this->console->extractNamespace($name, 1); + if (!$key) { + $key = self::GLOBAL_NAMESPACE; + } + + $namespacedCommands[$key][$name] = $command; + } + ksort($namespacedCommands); + + foreach ($namespacedCommands as &$commandsSet) { + ksort($commandsSet); + } + // unset reference to keep scope clear + unset($commandsSet); + + return $namespacedCommands; + } +} diff --git a/thinkphp/library/think/console/output/driver/Buffer.php b/thinkphp/library/think/console/output/driver/Buffer.php new file mode 100644 index 0000000..c77a2ec --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Buffer.php @@ -0,0 +1,52 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Buffer +{ + /** + * @var string + */ + private $buffer = ''; + + public function __construct(Output $output) + { + // do nothing + } + + public function fetch() + { + $content = $this->buffer; + $this->buffer = ''; + return $content; + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + $messages = (array) $messages; + + foreach ($messages as $message) { + $this->buffer .= $message; + } + if ($newline) { + $this->buffer .= "\n"; + } + } + + public function renderException(\Exception $e) + { + // do nothing + } + +} diff --git a/thinkphp/library/think/console/output/driver/Console.php b/thinkphp/library/think/console/output/driver/Console.php new file mode 100644 index 0000000..e041b52 --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Console.php @@ -0,0 +1,368 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; +use think\console\output\Formatter; + +class Console +{ + + /** @var Resource */ + private $stdout; + + /** @var Formatter */ + private $formatter; + + private $terminalDimensions; + + /** @var Output */ + private $output; + + public function __construct(Output $output) + { + $this->output = $output; + $this->formatter = new Formatter(); + $this->stdout = $this->openOutputStream(); + $decorated = $this->hasColorSupport($this->stdout); + $this->formatter->setDecorated($decorated); + } + + public function setDecorated($decorated) + { + $this->formatter->setDecorated($decorated); + } + + public function write($messages, $newline = false, $type = Output::OUTPUT_NORMAL, $stream = null) + { + if (Output::VERBOSITY_QUIET === $this->output->getVerbosity()) { + return; + } + + $messages = (array) $messages; + + foreach ($messages as $message) { + switch ($type) { + case Output::OUTPUT_NORMAL: + $message = $this->formatter->format($message); + break; + case Output::OUTPUT_RAW: + break; + case Output::OUTPUT_PLAIN: + $message = strip_tags($this->formatter->format($message)); + break; + default: + throw new \InvalidArgumentException(sprintf('Unknown output type given (%s)', $type)); + } + + $this->doWrite($message, $newline, $stream); + } + } + + public function renderException(\Exception $e) + { + $stderr = $this->openErrorStream(); + $decorated = $this->hasColorSupport($stderr); + $this->formatter->setDecorated($decorated); + + do { + $title = sprintf(' [%s] ', get_class($e)); + + $len = $this->stringWidth($title); + + $width = $this->getTerminalWidth() ? $this->getTerminalWidth() - 1 : PHP_INT_MAX; + + if (defined('HHVM_VERSION') && $width > 1 << 31) { + $width = 1 << 31; + } + $lines = []; + foreach (preg_split('/\r?\n/', $e->getMessage()) as $line) { + foreach ($this->splitStringByWidth($line, $width - 4) as $line) { + + $lineLength = $this->stringWidth(preg_replace('/\[[^m]*m/', '', $line)) + 4; + $lines[] = [$line, $lineLength]; + + $len = max($lineLength, $len); + } + } + + $messages = ['', '']; + $messages[] = $emptyLine = sprintf('%s', str_repeat(' ', $len)); + $messages[] = sprintf('%s%s', $title, str_repeat(' ', max(0, $len - $this->stringWidth($title)))); + foreach ($lines as $line) { + $messages[] = sprintf(' %s %s', $line[0], str_repeat(' ', $len - $line[1])); + } + $messages[] = $emptyLine; + $messages[] = ''; + $messages[] = ''; + + $this->write($messages, true, Output::OUTPUT_NORMAL, $stderr); + + if (Output::VERBOSITY_VERBOSE <= $this->output->getVerbosity()) { + $this->write('Exception trace:', true, Output::OUTPUT_NORMAL, $stderr); + + // exception related properties + $trace = $e->getTrace(); + array_unshift($trace, [ + 'function' => '', + 'file' => $e->getFile() !== null ? $e->getFile() : 'n/a', + 'line' => $e->getLine() !== null ? $e->getLine() : 'n/a', + 'args' => [], + ]); + + for ($i = 0, $count = count($trace); $i < $count; ++$i) { + $class = isset($trace[$i]['class']) ? $trace[$i]['class'] : ''; + $type = isset($trace[$i]['type']) ? $trace[$i]['type'] : ''; + $function = $trace[$i]['function']; + $file = isset($trace[$i]['file']) ? $trace[$i]['file'] : 'n/a'; + $line = isset($trace[$i]['line']) ? $trace[$i]['line'] : 'n/a'; + + $this->write(sprintf(' %s%s%s() at %s:%s', $class, $type, $function, $file, $line), true, Output::OUTPUT_NORMAL, $stderr); + } + + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + $this->write('', true, Output::OUTPUT_NORMAL, $stderr); + } + } while ($e = $e->getPrevious()); + + } + + /** + * 获取终端宽度 + * @return int|null + */ + protected function getTerminalWidth() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[0]; + } + + /** + * 获取终端高度 + * @return int|null + */ + protected function getTerminalHeight() + { + $dimensions = $this->getTerminalDimensions(); + + return $dimensions[1]; + } + + /** + * 获取当前终端的尺寸 + * @return array + */ + public function getTerminalDimensions() + { + if ($this->terminalDimensions) { + return $this->terminalDimensions; + } + + if ('\\' === DIRECTORY_SEPARATOR) { + if (preg_match('/^(\d+)x\d+ \(\d+x(\d+)\)$/', trim(getenv('ANSICON')), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + if (preg_match('/^(\d+)x(\d+)$/', $this->getMode(), $matches)) { + return [(int) $matches[1], (int) $matches[2]]; + } + } + + if ($sttyString = $this->getSttyColumns()) { + if (preg_match('/rows.(\d+);.columns.(\d+);/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + if (preg_match('/;.(\d+).rows;.(\d+).columns/i', $sttyString, $matches)) { + return [(int) $matches[2], (int) $matches[1]]; + } + } + + return [null, null]; + } + + /** + * 获取stty列数 + * @return string + */ + private function getSttyColumns() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('stty -a | grep columns', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + return $info; + } + return; + } + + /** + * 获取终端模式 + * @return string x 或 null + */ + private function getMode() + { + if (!function_exists('proc_open')) { + return; + } + + $descriptorspec = [1 => ['pipe', 'w'], 2 => ['pipe', 'w']]; + $process = proc_open('mode CON', $descriptorspec, $pipes, null, null, ['suppress_errors' => true]); + if (is_resource($process)) { + $info = stream_get_contents($pipes[1]); + fclose($pipes[1]); + fclose($pipes[2]); + proc_close($process); + + if (preg_match('/--------+\r?\n.+?(\d+)\r?\n.+?(\d+)\r?\n/', $info, $matches)) { + return $matches[2] . 'x' . $matches[1]; + } + } + return; + } + + private function stringWidth($string) + { + if (!function_exists('mb_strwidth')) { + return strlen($string); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return strlen($string); + } + + return mb_strwidth($string, $encoding); + } + + private function splitStringByWidth($string, $width) + { + if (!function_exists('mb_strwidth')) { + return str_split($string, $width); + } + + if (false === $encoding = mb_detect_encoding($string)) { + return str_split($string, $width); + } + + $utf8String = mb_convert_encoding($string, 'utf8', $encoding); + $lines = []; + $line = ''; + foreach (preg_split('//u', $utf8String) as $char) { + if (mb_strwidth($line . $char, 'utf8') <= $width) { + $line .= $char; + continue; + } + $lines[] = str_pad($line, $width); + $line = $char; + } + if (strlen($line)) { + $lines[] = count($lines) ? str_pad($line, $width) : $line; + } + + mb_convert_variables($encoding, 'utf8', $lines); + + return $lines; + } + + private function isRunningOS400() + { + $checks = [ + function_exists('php_uname') ? php_uname('s') : '', + getenv('OSTYPE'), + PHP_OS, + ]; + return false !== stripos(implode(';', $checks), 'OS400'); + } + + /** + * 当前环境是否支持写入控制台输出到stdout. + * + * @return bool + */ + protected function hasStdoutSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * 当前环境是否支持写入控制台输出到stderr. + * + * @return bool + */ + protected function hasStderrSupport() + { + return false === $this->isRunningOS400(); + } + + /** + * @return resource + */ + private function openOutputStream() + { + if (!$this->hasStdoutSupport()) { + return fopen('php://output', 'w'); + } + return @fopen('php://stdout', 'w') ?: fopen('php://output', 'w'); + } + + /** + * @return resource + */ + private function openErrorStream() + { + return fopen($this->hasStderrSupport() ? 'php://stderr' : 'php://output', 'w'); + } + + /** + * 将消息写入到输出。 + * @param string $message 消息 + * @param bool $newline 是否另起一行 + * @param null $stream + */ + protected function doWrite($message, $newline, $stream = null) + { + if (null === $stream) { + $stream = $this->stdout; + } + if (false === @fwrite($stream, $message . ($newline ? PHP_EOL : ''))) { + throw new \RuntimeException('Unable to write output.'); + } + + fflush($stream); + } + + /** + * 是否支持着色 + * @param $stream + * @return bool + */ + protected function hasColorSupport($stream) + { + if (DIRECTORY_SEPARATOR === '\\') { + return + '10.0.10586' === PHP_WINDOWS_VERSION_MAJOR . '.' . PHP_WINDOWS_VERSION_MINOR . '.' . PHP_WINDOWS_VERSION_BUILD + || false !== getenv('ANSICON') + || 'ON' === getenv('ConEmuANSI') + || 'xterm' === getenv('TERM'); + } + + return function_exists('posix_isatty') && @posix_isatty($stream); + } + +} diff --git a/thinkphp/library/think/console/output/driver/Nothing.php b/thinkphp/library/think/console/output/driver/Nothing.php new file mode 100644 index 0000000..9a55f77 --- /dev/null +++ b/thinkphp/library/think/console/output/driver/Nothing.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\driver; + +use think\console\Output; + +class Nothing +{ + + public function __construct(Output $output) + { + // do nothing + } + + public function write($messages, $newline = false, $options = Output::OUTPUT_NORMAL) + { + // do nothing + } + + public function renderException(\Exception $e) + { + // do nothing + } +} diff --git a/thinkphp/library/think/console/output/formatter/Stack.php b/thinkphp/library/think/console/output/formatter/Stack.php new file mode 100644 index 0000000..4864a3f --- /dev/null +++ b/thinkphp/library/think/console/output/formatter/Stack.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Stack +{ + + /** + * @var Style[] + */ + private $styles; + + /** + * @var Style + */ + private $emptyStyle; + + /** + * 构造方法 + * @param Style|null $emptyStyle + */ + public function __construct(Style $emptyStyle = null) + { + $this->emptyStyle = $emptyStyle ?: new Style(); + $this->reset(); + } + + /** + * 重置堆栈 + */ + public function reset() + { + $this->styles = []; + } + + /** + * 推一个样式进入堆栈 + * @param Style $style + */ + public function push(Style $style) + { + $this->styles[] = $style; + } + + /** + * 从堆栈中弹出一个样式 + * @param Style|null $style + * @return Style + * @throws \InvalidArgumentException + */ + public function pop(Style $style = null) + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + if (null === $style) { + return array_pop($this->styles); + } + + /** + * @var int $index + * @var Style $stackedStyle + */ + foreach (array_reverse($this->styles, true) as $index => $stackedStyle) { + if ($style->apply('') === $stackedStyle->apply('')) { + $this->styles = array_slice($this->styles, 0, $index); + + return $stackedStyle; + } + } + + throw new \InvalidArgumentException('Incorrectly nested style tag found.'); + } + + /** + * 计算堆栈的当前样式。 + * @return Style + */ + public function getCurrent() + { + if (empty($this->styles)) { + return $this->emptyStyle; + } + + return $this->styles[count($this->styles) - 1]; + } + + /** + * @param Style $emptyStyle + * @return Stack + */ + public function setEmptyStyle(Style $emptyStyle) + { + $this->emptyStyle = $emptyStyle; + + return $this; + } + + /** + * @return Style + */ + public function getEmptyStyle() + { + return $this->emptyStyle; + } +} diff --git a/thinkphp/library/think/console/output/formatter/Style.php b/thinkphp/library/think/console/output/formatter/Style.php new file mode 100644 index 0000000..d9b0999 --- /dev/null +++ b/thinkphp/library/think/console/output/formatter/Style.php @@ -0,0 +1,189 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\formatter; + +class Style +{ + + private static $availableForegroundColors = [ + 'black' => ['set' => 30, 'unset' => 39], + 'red' => ['set' => 31, 'unset' => 39], + 'green' => ['set' => 32, 'unset' => 39], + 'yellow' => ['set' => 33, 'unset' => 39], + 'blue' => ['set' => 34, 'unset' => 39], + 'magenta' => ['set' => 35, 'unset' => 39], + 'cyan' => ['set' => 36, 'unset' => 39], + 'white' => ['set' => 37, 'unset' => 39], + ]; + private static $availableBackgroundColors = [ + 'black' => ['set' => 40, 'unset' => 49], + 'red' => ['set' => 41, 'unset' => 49], + 'green' => ['set' => 42, 'unset' => 49], + 'yellow' => ['set' => 43, 'unset' => 49], + 'blue' => ['set' => 44, 'unset' => 49], + 'magenta' => ['set' => 45, 'unset' => 49], + 'cyan' => ['set' => 46, 'unset' => 49], + 'white' => ['set' => 47, 'unset' => 49], + ]; + private static $availableOptions = [ + 'bold' => ['set' => 1, 'unset' => 22], + 'underscore' => ['set' => 4, 'unset' => 24], + 'blink' => ['set' => 5, 'unset' => 25], + 'reverse' => ['set' => 7, 'unset' => 27], + 'conceal' => ['set' => 8, 'unset' => 28], + ]; + + private $foreground; + private $background; + private $options = []; + + /** + * 初始化输出的样式 + * @param string|null $foreground 字体颜色 + * @param string|null $background 背景色 + * @param array $options 格式 + * @api + */ + public function __construct($foreground = null, $background = null, array $options = []) + { + if (null !== $foreground) { + $this->setForeground($foreground); + } + if (null !== $background) { + $this->setBackground($background); + } + if (count($options)) { + $this->setOptions($options); + } + } + + /** + * 设置字体颜色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setForeground($color = null) + { + if (null === $color) { + $this->foreground = null; + + return; + } + + if (!isset(static::$availableForegroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid foreground color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableForegroundColors)))); + } + + $this->foreground = static::$availableForegroundColors[$color]; + } + + /** + * 设置背景色 + * @param string|null $color 颜色名 + * @throws \InvalidArgumentException + * @api + */ + public function setBackground($color = null) + { + if (null === $color) { + $this->background = null; + + return; + } + + if (!isset(static::$availableBackgroundColors[$color])) { + throw new \InvalidArgumentException(sprintf('Invalid background color specified: "%s". Expected one of (%s)', $color, implode(', ', array_keys(static::$availableBackgroundColors)))); + } + + $this->background = static::$availableBackgroundColors[$color]; + } + + /** + * 设置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException When the option name isn't defined + * @api + */ + public function setOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + if (!in_array(static::$availableOptions[$option], $this->options)) { + $this->options[] = static::$availableOptions[$option]; + } + } + + /** + * 重置字体格式 + * @param string $option 格式名 + * @throws \InvalidArgumentException + */ + public function unsetOption($option) + { + if (!isset(static::$availableOptions[$option])) { + throw new \InvalidArgumentException(sprintf('Invalid option specified: "%s". Expected one of (%s)', $option, implode(', ', array_keys(static::$availableOptions)))); + } + + $pos = array_search(static::$availableOptions[$option], $this->options); + if (false !== $pos) { + unset($this->options[$pos]); + } + } + + /** + * 批量设置字体格式 + * @param array $options + */ + public function setOptions(array $options) + { + $this->options = []; + + foreach ($options as $option) { + $this->setOption($option); + } + } + + /** + * 应用样式到文字 + * @param string $text 文字 + * @return string + */ + public function apply($text) + { + $setCodes = []; + $unsetCodes = []; + + if (null !== $this->foreground) { + $setCodes[] = $this->foreground['set']; + $unsetCodes[] = $this->foreground['unset']; + } + if (null !== $this->background) { + $setCodes[] = $this->background['set']; + $unsetCodes[] = $this->background['unset']; + } + if (count($this->options)) { + foreach ($this->options as $option) { + $setCodes[] = $option['set']; + $unsetCodes[] = $option['unset']; + } + } + + if (0 === count($setCodes)) { + return $text; + } + + return sprintf("\033[%sm%s\033[%sm", implode(';', $setCodes), $text, implode(';', $unsetCodes)); + } +} diff --git a/thinkphp/library/think/console/output/question/Choice.php b/thinkphp/library/think/console/output/question/Choice.php new file mode 100644 index 0000000..cdc3b4e --- /dev/null +++ b/thinkphp/library/think/console/output/question/Choice.php @@ -0,0 +1,163 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Choice extends Question +{ + + private $choices; + private $multiselect = false; + private $prompt = ' > '; + private $errorMessage = 'Value "%s" is invalid'; + + /** + * 构造方法 + * @param string $question 问题 + * @param array $choices 选项 + * @param mixed $default 默认答案 + */ + public function __construct($question, array $choices, $default = null) + { + parent::__construct($question, $default); + + $this->choices = $choices; + $this->setValidator($this->getDefaultValidator()); + $this->setAutocompleterValues($choices); + } + + /** + * 可选项 + * @return array + */ + public function getChoices() + { + return $this->choices; + } + + /** + * 设置可否多选 + * @param bool $multiselect + * @return self + */ + public function setMultiselect($multiselect) + { + $this->multiselect = $multiselect; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + public function isMultiselect() + { + return $this->multiselect; + } + + /** + * 获取提示 + * @return string + */ + public function getPrompt() + { + return $this->prompt; + } + + /** + * 设置提示 + * @param string $prompt + * @return self + */ + public function setPrompt($prompt) + { + $this->prompt = $prompt; + + return $this; + } + + /** + * 设置错误提示信息 + * @param string $errorMessage + * @return self + */ + public function setErrorMessage($errorMessage) + { + $this->errorMessage = $errorMessage; + $this->setValidator($this->getDefaultValidator()); + + return $this; + } + + /** + * 获取默认的验证方法 + * @return callable + */ + private function getDefaultValidator() + { + $choices = $this->choices; + $errorMessage = $this->errorMessage; + $multiselect = $this->multiselect; + $isAssoc = $this->isAssoc($choices); + + return function ($selected) use ($choices, $errorMessage, $multiselect, $isAssoc) { + // Collapse all spaces. + $selectedChoices = str_replace(' ', '', $selected); + + if ($multiselect) { + // Check for a separated comma values + if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) { + throw new \InvalidArgumentException(sprintf($errorMessage, $selected)); + } + $selectedChoices = explode(',', $selectedChoices); + } else { + $selectedChoices = [$selected]; + } + + $multiselectChoices = []; + foreach ($selectedChoices as $value) { + $results = []; + foreach ($choices as $key => $choice) { + if ($choice === $value) { + $results[] = $key; + } + } + + if (count($results) > 1) { + throw new \InvalidArgumentException(sprintf('The provided answer is ambiguous. Value should be one of %s.', implode(' or ', $results))); + } + + $result = array_search($value, $choices); + + if (!$isAssoc) { + if (!empty($result)) { + $result = $choices[$result]; + } elseif (isset($choices[$value])) { + $result = $choices[$value]; + } + } elseif (empty($result) && array_key_exists($value, $choices)) { + $result = $value; + } + + if (false === $result) { + throw new \InvalidArgumentException(sprintf($errorMessage, $value)); + } + array_push($multiselectChoices, $result); + } + + if ($multiselect) { + return $multiselectChoices; + } + + return current($multiselectChoices); + }; + } +} diff --git a/thinkphp/library/think/console/output/question/Confirmation.php b/thinkphp/library/think/console/output/question/Confirmation.php new file mode 100644 index 0000000..6598f9b --- /dev/null +++ b/thinkphp/library/think/console/output/question/Confirmation.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\console\output\question; + +use think\console\output\Question; + +class Confirmation extends Question +{ + + private $trueAnswerRegex; + + /** + * 构造方法 + * @param string $question 问题 + * @param bool $default 默认答案 + * @param string $trueAnswerRegex 验证正则 + */ + public function __construct($question, $default = true, $trueAnswerRegex = '/^y/i') + { + parent::__construct($question, (bool) $default); + + $this->trueAnswerRegex = $trueAnswerRegex; + $this->setNormalizer($this->getDefaultNormalizer()); + } + + /** + * 获取默认的答案回调 + * @return callable + */ + private function getDefaultNormalizer() + { + $default = $this->getDefault(); + $regex = $this->trueAnswerRegex; + + return function ($answer) use ($default, $regex) { + if (is_bool($answer)) { + return $answer; + } + + $answerIsTrue = (bool) preg_match($regex, $answer); + if (false === $default) { + return $answer && $answerIsTrue; + } + + return !$answer || $answerIsTrue; + }; + } +} diff --git a/thinkphp/library/think/db/Builder.php b/thinkphp/library/think/db/Builder.php new file mode 100644 index 0000000..05d52d1 --- /dev/null +++ b/thinkphp/library/think/db/Builder.php @@ -0,0 +1,1161 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Exception; + +abstract class Builder +{ + // connection对象实例 + protected $connection; + + // 查询表达式映射 + protected $exp = ['EQ' => '=', 'NEQ' => '<>', 'GT' => '>', 'EGT' => '>=', 'LT' => '<', 'ELT' => '<=', 'NOTLIKE' => 'NOT LIKE', 'NOTIN' => 'NOT IN', 'NOTBETWEEN' => 'NOT BETWEEN', 'NOTEXISTS' => 'NOT EXISTS', 'NOTNULL' => 'NOT NULL', 'NOTBETWEEN TIME' => 'NOT BETWEEN TIME']; + + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + // SQL表达式 + protected $selectSql = 'SELECT%DISTINCT% %FIELD% FROM %TABLE%%FORCE%%JOIN%%WHERE%%GROUP%%HAVING%%UNION%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $insertSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + protected $updateSql = 'UPDATE %TABLE% SET %SET%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + protected $deleteSql = 'DELETE FROM %TABLE%%USING%%JOIN%%WHERE%%ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 架构函数 + * @access public + * @param Connection $connection 数据库连接对象实例 + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * 获取当前的连接对象实例 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 注册查询表达式解析 + * @access public + * @param string $name 解析方法 + * @param array $parser 匹配表达式数据 + * @return $this + */ + public function bindParser($name, $parser) + { + $this->parser[$name] = $parser; + return $this; + } + + /** + * 数据分析 + * @access protected + * @param Query $query 查询对象 + * @param array $data 数据 + * @param array $fields 字段信息 + * @param array $bind 参数绑定 + * @return array + */ + protected function parseData(Query $query, $data = [], $fields = [], $bind = []) + { + if (empty($data)) { + return []; + } + + $options = $query->getOptions(); + + // 获取绑定信息 + if (empty($bind)) { + $bind = $this->connection->getFieldsBind($options['table']); + } + + if (empty($fields)) { + if ('*' == $options['field']) { + $fields = array_keys($bind); + } else { + $fields = $options['field']; + } + } + + $result = []; + + foreach ($data as $key => $val) { + if ('*' != $options['field'] && !in_array($key, $fields, true)) { + continue; + } + + $item = $this->parseKey($query, $key, true); + + if ($val instanceof Expression) { + $result[$item] = $val->getValue(); + continue; + } elseif (!is_scalar($val) && (in_array($key, (array) $query->getOptions('json')) || 'json' == $this->connection->getFieldsType($options['table'], $key))) { + $val = json_encode($val, JSON_UNESCAPED_UNICODE); + } elseif (is_object($val) && method_exists($val, '__toString')) { + // 对象数据写入 + $val = $val->__toString(); + } + + if (false !== strpos($key, '->')) { + list($key, $name) = explode('->', $key); + $item = $this->parseKey($query, $key); + $result[$item] = 'json_set(' . $item . ', \'$.' . $name . '\', ' . $this->parseDataBind($query, $key, $val, $bind) . ')'; + } elseif ('*' == $options['field'] && false === strpos($key, '.') && !in_array($key, $fields, true)) { + if ($options['strict']) { + throw new Exception('fields not exists:[' . $key . ']'); + } + } elseif (is_null($val)) { + $result[$item] = 'NULL'; + } elseif (is_array($val) && !empty($val)) { + switch (strtoupper($val[0])) { + case 'INC': + $result[$item] = $item . ' + ' . floatval($val[1]); + break; + case 'DEC': + $result[$item] = $item . ' - ' . floatval($val[1]); + break; + case 'EXP': + throw new Exception('not support data:[' . $val[0] . ']'); + } + } elseif (is_scalar($val)) { + // 过滤非标量数据 + $result[$item] = $this->parseDataBind($query, $key, $val, $bind); + } + } + + return $result; + } + + /** + * 数据绑定处理 + * @access protected + * @param Query $query 查询对象 + * @param string $key 字段名 + * @param mixed $data 数据 + * @param array $bind 绑定数据 + * @return string + */ + protected function parseDataBind(Query $query, $key, $data, $bind = []) + { + if ($data instanceof Expression) { + return $data->getValue(); + } + + $name = $query->bind($data, isset($bind[$key]) ? $bind[$key] : PDO::PARAM_STR); + + return ':' . $name; + } + + /** + * 字段名分析 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + return $key instanceof Expression ? $key->getValue() : $key; + } + + /** + * field分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $fields 字段名 + * @return string + */ + protected function parseField(Query $query, $fields) + { + if ('*' == $fields || empty($fields)) { + $fieldsStr = '*'; + } elseif (is_array($fields)) { + // 支持 'field1'=>'field2' 这样的字段别名定义 + $array = []; + + foreach ($fields as $key => $field) { + if (!is_numeric($key)) { + $array[] = $this->parseKey($query, $key) . ' AS ' . $this->parseKey($query, $field, true); + } else { + $array[] = $this->parseKey($query, $field); + } + } + + $fieldsStr = implode(',', $array); + } + + return $fieldsStr; + } + + /** + * table分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $tables 表名 + * @return string + */ + protected function parseTable(Query $query, $tables) + { + $item = []; + $options = $query->getOptions(); + + foreach ((array) $tables as $key => $table) { + if (!is_numeric($key)) { + $key = $this->connection->parseSqlTable($key); + $item[] = $this->parseKey($query, $key) . ' ' . $this->parseKey($query, $table); + } else { + $table = $this->connection->parseSqlTable($table); + + if (isset($options['alias'][$table])) { + $item[] = $this->parseKey($query, $table) . ' ' . $this->parseKey($query, $options['alias'][$table]); + } else { + $item[] = $this->parseKey($query, $table); + } + } + } + + return implode(',', $item); + } + + /** + * where分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + protected function parseWhere(Query $query, $where) + { + $options = $query->getOptions(); + $whereStr = $this->buildWhere($query, $where); + + if (!empty($options['soft_delete'])) { + // 附加软删除条件 + list($field, $condition) = $options['soft_delete']; + + $binds = $this->connection->getFieldsBind($options['table']); + $whereStr = $whereStr ? '( ' . $whereStr . ' ) AND ' : ''; + $whereStr = $whereStr . $this->parseWhereItem($query, $field, $condition, '', $binds); + } + + return empty($whereStr) ? '' : ' WHERE ' . $whereStr; + } + + /** + * 生成查询条件SQL + * @access public + * @param Query $query 查询对象 + * @param mixed $where 查询条件 + * @return string + */ + public function buildWhere(Query $query, $where) + { + if (empty($where)) { + $where = []; + } + + $whereStr = ''; + $binds = $this->connection->getFieldsBind($query->getOptions('table')); + + foreach ($where as $logic => $val) { + $str = []; + + foreach ($val as $value) { + if ($value instanceof Expression) { + $str[] = ' ' . $logic . ' ( ' . $value->getValue() . ' )'; + continue; + } + + if (is_array($value)) { + if (key($value) !== 0) { + throw new Exception('where express error:' . var_export($value, true)); + } + $field = array_shift($value); + } elseif (!($value instanceof \Closure)) { + throw new Exception('where express error:' . var_export($value, true)); + } + + if ($value instanceof \Closure) { + // 使用闭包查询 + $newQuery = $query->newQuery()->setConnection($this->connection); + $value($newQuery); + $whereClause = $this->buildWhere($query, $newQuery->getOptions('where')); + + if (!empty($whereClause)) { + $str[] = ' ' . $logic . ' ( ' . $whereClause . ' )'; + } + } elseif (is_array($field)) { + array_unshift($value, $field); + $str2 = []; + foreach ($value as $item) { + $str2[] = $this->parseWhereItem($query, array_shift($item), $item, $logic, $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $str2) . ' )'; + } elseif (strpos($field, '|')) { + // 不同字段使用相同查询条件(OR) + $array = explode('|', $field); + $item = []; + + foreach ($array as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' OR ', $item) . ' )'; + } elseif (strpos($field, '&')) { + // 不同字段使用相同查询条件(AND) + $array = explode('&', $field); + $item = []; + + foreach ($array as $k) { + $item[] = $this->parseWhereItem($query, $k, $value, '', $binds); + } + + $str[] = ' ' . $logic . ' ( ' . implode(' AND ', $item) . ' )'; + } else { + // 对字段使用表达式查询 + $field = is_string($field) ? $field : ''; + $str[] = ' ' . $logic . ' ' . $this->parseWhereItem($query, $field, $value, $logic, $binds); + } + } + + $whereStr .= empty($whereStr) ? substr(implode(' ', $str), strlen($logic) + 1) : implode(' ', $str); + } + + return $whereStr; + } + + // where子单元分析 + protected function parseWhereItem(Query $query, $field, $val, $rule = '', $binds = []) + { + // 字段分析 + $key = $field ? $this->parseKey($query, $field, true) : ''; + + // 查询规则和条件 + if (!is_array($val)) { + $val = is_null($val) ? ['NULL', ''] : ['=', $val]; + } + + list($exp, $value) = $val; + + // 对一个字段使用多个查询条件 + if (is_array($exp)) { + $item = array_pop($val); + + // 传入 or 或者 and + if (is_string($item) && in_array($item, ['AND', 'and', 'OR', 'or'])) { + $rule = $item; + } else { + array_push($val, $item); + } + + foreach ($val as $k => $item) { + $str[] = $this->parseWhereItem($query, $field, $item, $rule, $binds); + } + + return '( ' . implode(' ' . $rule . ' ', $str) . ' )'; + } + + // 检测操作符 + $exp = strtoupper($exp); + if (isset($this->exp[$exp])) { + $exp = $this->exp[$exp]; + } + + if ($value instanceof Expression) { + + } elseif (is_object($value) && method_exists($value, '__toString')) { + // 对象数据写入 + $value = $value->__toString(); + } + + if (strpos($field, '->')) { + $jsonType = $query->getJsonFieldType($field); + $bindType = $this->connection->getFieldBindType($jsonType); + } else { + $bindType = isset($binds[$field]) ? $binds[$field] : PDO::PARAM_STR; + } + + if (is_scalar($value) && !in_array($exp, ['EXP', 'NOT NULL', 'NULL', 'IN', 'NOT IN', 'BETWEEN', 'NOT BETWEEN']) && strpos($exp, 'TIME') === false) { + $name = $query->bind($value, $bindType); + $value = ':' . $name; + } + + // 解析查询表达式 + foreach ($this->parser as $fun => $parse) { + if (in_array($exp, $parse)) { + $whereStr = $this->$fun($query, $key, $exp, $value, $field, $bindType, isset($val[2]) ? $val[2] : 'AND'); + break; + } + } + + if (!isset($whereStr)) { + throw new Exception('where express error:' . $exp); + } + + return $whereStr; + } + + /** + * 模糊查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @param string $logic + * @return string + */ + protected function parseLike(Query $query, $key, $exp, $value, $field, $bindType, $logic) + { + // 模糊匹配 + if (is_array($value)) { + foreach ($value as $item) { + $name = $query->bind($item, $bindType); + $array[] = $key . ' ' . $exp . ' :' . $name; + } + + $whereStr = '(' . implode($array, ' ' . strtoupper($logic) . ' ') . ')'; + } else { + $whereStr = $key . ' ' . $exp . ' ' . $value; + } + + return $whereStr; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param array $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseColumn(Query $query, $key, $exp, array $value, $field, $bindType) + { + // 字段比较查询 + list($op, $field2) = $value; + + if (!in_array($op, ['=', '<>', '>', '>=', '<', '<='])) { + throw new Exception('where express error:' . var_export($value, true)); + } + + return '( ' . $key . ' ' . $op . ' ' . $this->parseKey($query, $field2, true) . ' )'; + } + + /** + * 表达式查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param Expression $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExp(Query $query, $key, $exp, Expression $value, $field, $bindType) + { + // 表达式查询 + return '( ' . $key . ' ' . $value->getValue() . ' )'; + } + + /** + * Null查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseNull(Query $query, $key, $exp, $value, $field, $bindType) + { + // NULL 查询 + return $key . ' IS ' . $exp; + } + + /** + * 范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetween(Query $query, $key, $exp, $value, $field, $bindType) + { + // BETWEEN 查询 + $data = is_array($value) ? $value : explode(',', $value); + + $min = $query->bind($data[0], $bindType); + $max = $query->bind($data[1], $bindType); + + return $key . ' ' . $exp . ' :' . $min . ' AND :' . $max . ' '; + } + + /** + * Exists查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseExists(Query $query, $key, $exp, $value, $field, $bindType) + { + // EXISTS 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } elseif ($value instanceof Expression) { + $value = $value->getValue(); + } else { + throw new Exception('where express error:' . $value); + } + + return $exp . ' (' . $value . ')'; + } + + /** + * 时间比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseTime(Query $query, $key, $exp, $value, $field, $bindType) + { + return $key . ' ' . substr($exp, 0, 2) . ' ' . $this->parseDateTime($query, $value, $field, $bindType); + } + + /** + * 大小比较查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseCompare(Query $query, $key, $exp, $value, $field, $bindType) + { + if (is_array($value)) { + throw new Exception('where express error:' . $exp . var_export($value, true)); + } + + // 比较运算 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value); + } + + return $key . ' ' . $exp . ' ' . $value; + } + + /** + * 时间范围查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseBetweenTime(Query $query, $key, $exp, $value, $field, $bindType) + { + if (is_string($value)) { + $value = explode(',', $value); + } + + return $key . ' ' . substr($exp, 0, -4) + . $this->parseDateTime($query, $value[0], $field, $bindType) + . ' AND ' + . $this->parseDateTime($query, $value[1], $field, $bindType); + + } + + /** + * IN查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param mixed $value + * @param string $field + * @param integer $bindType + * @return string + */ + protected function parseIn(Query $query, $key, $exp, $value, $field, $bindType) + { + // IN 查询 + if ($value instanceof \Closure) { + $value = $this->parseClosure($query, $value, false); + } else { + $value = array_unique(is_array($value) ? $value : explode(',', $value)); + + $array = []; + + foreach ($value as $k => $v) { + $name = $query->bind($v, $bindType); + $array[] = ':' . $name; + } + + $zone = implode(',', $array); + + $value = empty($zone) ? "''" : $zone; + } + + return $key . ' ' . $exp . ' (' . $value . ')'; + } + + /** + * 闭包子查询 + * @access protected + * @param Query $query 查询对象 + * @param \Closure $call + * @param bool $show + * @return string + */ + protected function parseClosure(Query $query, $call, $show = true) + { + $newQuery = $query->newQuery()->setConnection($this->connection); + $call($newQuery); + + return $newQuery->buildSql($show); + } + + /** + * 日期时间条件解析 + * @access protected + * @param Query $query 查询对象 + * @param string $value + * @param string $key + * @param integer $bindType + * @return string + */ + protected function parseDateTime(Query $query, $value, $key, $bindType = null) + { + $options = $query->getOptions(); + + // 获取时间字段类型 + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key); + + if (isset($options['alias']) && $pos = array_search($table, $options['alias'])) { + $table = $pos; + } + } else { + $table = $options['table']; + } + + $type = $this->connection->getTableInfo($table, 'type'); + + if (isset($type[$key])) { + $info = $type[$key]; + } + + if (isset($info)) { + if (is_string($value)) { + $value = strtotime($value) ?: $value; + } + + if (preg_match('/(datetime|timestamp)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d H:i:s', $value); + } elseif (preg_match('/(date)/is', $info)) { + // 日期及时间戳类型 + $value = date('Y-m-d', $value); + } + } + + $name = $query->bind($value, $bindType); + + return ':' . $name; + } + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, $limit) + { + return (!empty($limit) && false === strpos($limit, '(')) ? ' LIMIT ' . $limit . ' ' : ''; + } + + /** + * join分析 + * @access protected + * @param Query $query 查询对象 + * @param array $join + * @return string + */ + protected function parseJoin(Query $query, $join) + { + $joinStr = ''; + + if (!empty($join)) { + foreach ($join as $item) { + list($table, $type, $on) = $item; + + $condition = []; + + foreach ((array) $on as $val) { + if ($val instanceof Expression) { + $condition[] = $val->getValue(); + } elseif (strpos($val, '=')) { + list($val1, $val2) = explode('=', $val, 2); + + $condition[] = $this->parseKey($query, $val1) . '=' . $this->parseKey($query, $val2); + } else { + $condition[] = $val; + } + } + + $table = $this->parseTable($query, $table); + + $joinStr .= ' ' . $type . ' JOIN ' . $table . ' ON ' . implode(' AND ', $condition); + } + } + + return $joinStr; + } + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, $order) + { + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif (is_array($val) && preg_match('/^[\w\.]+$/', $key)) { + $array[] = $this->parseOrderField($query, $key, $val); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } elseif (is_string($val)) { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * orderField分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $key + * @param array $val + * @return string + */ + protected function parseOrderField($query, $key, $val) + { + if (isset($val['sort'])) { + $sort = $val['sort']; + unset($val['sort']); + } else { + $sort = ''; + } + + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + + $options = $query->getOptions(); + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($val as $k => $item) { + $val[$k] = $this->parseDataBind($query, $key, $item, $bind); + } + + return 'field(' . $this->parseKey($query, $key, true) . ',' . implode(',', $val) . ')' . $sort; + } + + /** + * group分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $group + * @return string + */ + protected function parseGroup(Query $query, $group) + { + if (empty($group)) { + return ''; + } + + if (is_string($group)) { + $group = explode(',', $group); + } + + foreach ($group as $key) { + $val[] = $this->parseKey($query, $key); + } + + return ' GROUP BY ' . implode(',', $val); + } + + /** + * having分析 + * @access protected + * @param Query $query 查询对象 + * @param string $having + * @return string + */ + protected function parseHaving(Query $query, $having) + { + return !empty($having) ? ' HAVING ' . $having : ''; + } + + /** + * comment分析 + * @access protected + * @param Query $query 查询对象 + * @param string $comment + * @return string + */ + protected function parseComment(Query $query, $comment) + { + if (false !== strpos($comment, '*/')) { + $comment = strstr($comment, '*/', true); + } + + return !empty($comment) ? ' /* ' . $comment . ' */' : ''; + } + + /** + * distinct分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $distinct + * @return string + */ + protected function parseDistinct(Query $query, $distinct) + { + return !empty($distinct) ? ' DISTINCT ' : ''; + } + + /** + * union分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $union + * @return string + */ + protected function parseUnion(Query $query, $union) + { + if (empty($union)) { + return ''; + } + + $type = $union['type']; + unset($union['type']); + + foreach ($union as $u) { + if ($u instanceof \Closure) { + $sql[] = $type . ' ' . $this->parseClosure($query, $u); + } elseif (is_string($u)) { + $sql[] = $type . ' ( ' . $this->connection->parseSqlTable($u) . ' )'; + } + } + + return ' ' . implode(' ', $sql); + } + + /** + * index分析,可在操作链中指定需要强制使用的索引 + * @access protected + * @param Query $query 查询对象 + * @param mixed $index + * @return string + */ + protected function parseForce(Query $query, $index) + { + if (empty($index)) { + return ''; + } + + return sprintf(" FORCE INDEX ( %s ) ", is_array($index) ? implode(',', $index) : $index); + } + + /** + * 设置锁机制 + * @access protected + * @param Query $query 查询对象 + * @param bool|string $lock + * @return string + */ + protected function parseLock(Query $query, $lock = false) + { + if (is_bool($lock)) { + return $lock ? ' FOR UPDATE ' : ''; + } elseif (is_string($lock) && !empty($lock)) { + return ' ' . trim($lock) . ' '; + } + } + + /** + * 生成查询SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function select(Query $query) + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%DISTINCT%', '%FIELD%', '%JOIN%', '%WHERE%', '%GROUP%', '%HAVING%', '%ORDER%', '%LIMIT%', '%UNION%', '%LOCK%', '%COMMENT%', '%FORCE%'], + [ + $this->parseTable($query, $options['table']), + $this->parseDistinct($query, $options['distinct']), + $this->parseField($query, $options['field']), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseGroup($query, $options['group']), + $this->parseHaving($query, $options['having']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseUnion($query, $options['union']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + $this->parseForce($query, $options['force']), + ], + $this->selectSql); + } + + /** + * 生成Insert SQL + * @access public + * @param Query $query 查询对象 + * @param bool $replace 是否replace + * @return string + */ + public function insert(Query $query, $replace = false) + { + $options = $query->getOptions(); + + // 分析并处理数据 + $data = $this->parseData($query, $options['data']); + if (empty($data)) { + return ''; + } + + $fields = array_keys($data); + $values = array_values($data); + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertSql); + } + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, $dataSet, $replace = false) + { + $options = $query->getOptions(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = $this->connection->getTableFields($options['table']); + } else { + $allowFields = $options['field']; + } + + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $data) { + $data = $this->parseData($query, $data, $allowFields, $bind); + + $values[] = 'SELECT ' . implode(',', array_values($data)); + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + $fields = []; + + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + implode(' , ', $fields), + implode(' UNION ALL ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 生成slect insert SQL + * @access public + * @param Query $query 查询对象 + * @param array $fields 数据 + * @param string $table 数据表 + * @return string + */ + public function selectInsert(Query $query, $fields, $table) + { + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + $field = $this->parseKey($query, $field, true); + } + + return 'INSERT INTO ' . $this->parseTable($query, $table) . ' (' . implode(',', $fields) . ') ' . $this->select($query); + } + + /** + * 生成update SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function update(Query $query) + { + $options = $query->getOptions(); + + $data = $this->parseData($query, $options['data']); + + if (empty($data)) { + return ''; + } + + foreach ($data as $key => $val) { + $set[] = $key . ' = ' . $val; + } + + return str_replace( + ['%TABLE%', '%SET%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + implode(' , ', $set), + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->updateSql); + } + + /** + * 生成delete SQL + * @access public + * @param Query $query 查询对象 + * @return string + */ + public function delete(Query $query) + { + $options = $query->getOptions(); + + return str_replace( + ['%TABLE%', '%USING%', '%JOIN%', '%WHERE%', '%ORDER%', '%LIMIT%', '%LOCK%', '%COMMENT%'], + [ + $this->parseTable($query, $options['table']), + !empty($options['using']) ? ' USING ' . $this->parseTable($query, $options['using']) . ' ' : '', + $this->parseJoin($query, $options['join']), + $this->parseWhere($query, $options['where']), + $this->parseOrder($query, $options['order']), + $this->parseLimit($query, $options['limit']), + $this->parseLock($query, $options['lock']), + $this->parseComment($query, $options['comment']), + ], + $this->deleteSql); + } +} diff --git a/thinkphp/library/think/db/Connection.php b/thinkphp/library/think/db/Connection.php new file mode 100644 index 0000000..af27fd6 --- /dev/null +++ b/thinkphp/library/think/db/Connection.php @@ -0,0 +1,2150 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use InvalidArgumentException; +use PDO; +use PDOStatement; +use think\Container; +use think\Db; +use think\db\exception\BindParamException; +use think\Debug; +use think\Exception; +use think\exception\PDOException; +use think\Loader; + +abstract class Connection +{ + const PARAM_FLOAT = 21; + protected static $instance = []; + /** @var PDOStatement PDO操作实例 */ + protected $PDOStatement; + + /** @var string 当前SQL指令 */ + protected $queryStr = ''; + // 返回或者影响记录数 + protected $numRows = 0; + // 事务指令数 + protected $transTimes = 0; + // 错误信息 + protected $error = ''; + + /** @var PDO[] 数据库连接ID 支持多个连接 */ + protected $links = []; + + /** @var PDO 当前连接ID */ + protected $linkID; + protected $linkRead; + protected $linkWrite; + + // 查询结果类型 + protected $fetchType = PDO::FETCH_ASSOC; + // 字段属性大小写 + protected $attrCase = PDO::CASE_LOWER; + // 监听回调 + protected static $event = []; + + // 数据表信息 + protected static $info = []; + + // 使用Builder类 + protected $builderClassName; + // Builder对象 + protected $builder; + // 数据库连接参数配置 + protected $config = [ + // 数据库类型 + 'type' => '', + // 服务器地址 + 'hostname' => '', + // 数据库名 + 'database' => '', + // 用户名 + 'username' => '', + // 密码 + 'password' => '', + // 端口 + 'hostport' => '', + // 连接dsn + 'dsn' => '', + // 数据库连接参数 + 'params' => [], + // 数据库编码默认采用utf8 + 'charset' => 'utf8', + // 数据库表前缀 + 'prefix' => '', + // 数据库调试模式 + 'debug' => false, + // 数据库部署方式:0 集中式(单一服务器),1 分布式(主从服务器) + 'deploy' => 0, + // 数据库读写是否分离 主从式有效 + 'rw_separate' => false, + // 读写分离后 主服务器数量 + 'master_num' => 1, + // 指定从服务器序号 + 'slave_no' => '', + // 模型写入后自动读取主服务器 + 'read_master' => false, + // 是否严格检查字段是否存在 + 'fields_strict' => true, + // 数据集返回类型 + 'resultset_type' => '', + // 自动写入时间戳字段 + 'auto_timestamp' => false, + // 时间字段取出后的默认时间格式 + 'datetime_format' => 'Y-m-d H:i:s', + // 是否需要进行SQL性能分析 + 'sql_explain' => false, + // Builder类 + 'builder' => '', + // Query类 + 'query' => '\\think\\db\\Query', + // 是否需要断线重连 + 'break_reconnect' => false, + // 断线标识字符串 + 'break_match_str' => [], + ]; + + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + PDO::ATTR_EMULATE_PREPARES => false, + ]; + + // 服务器断线标识字符 + protected $breakMatchStr = [ + 'server has gone away', + 'no connection to the server', + 'Lost connection', + 'is dead or not enabled', + 'Error while sending', + 'decryption failed or bad record mac', + 'server closed the connection unexpectedly', + 'SSL connection has been closed unexpectedly', + 'Error writing data to the connection', + 'Resource deadlock avoided', + 'failed with errno', + ]; + + // 绑定参数 + protected $bind = []; + + /** + * 架构函数 读取数据库配置信息 + * @access public + * @param array $config 数据库配置数组 + */ + public function __construct(array $config = []) + { + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + + // 创建Builder对象 + $class = $this->getBuilderClass(); + + $this->builder = new $class($this); + + // 执行初始化操作 + $this->initialize(); + } + + /** + * 初始化 + * @access protected + * @return void + */ + protected function initialize() + {} + + /** + * 取得数据库连接类实例 + * @access public + * @param mixed $config 连接配置 + * @param bool|string $name 连接标识 true 强制重新连接 + * @return Connection + * @throws Exception + */ + public static function instance($config = [], $name = false) + { + if (false === $name) { + $name = md5(serialize($config)); + } + + if (true === $name || !isset(self::$instance[$name])) { + if (empty($config['type'])) { + throw new InvalidArgumentException('Undefined db type'); + } + + // 记录初始化信息 + Container::get('app')->log('[ DB ] INIT ' . $config['type']); + + if (true === $name) { + $name = md5(serialize($config)); + } + + self::$instance[$name] = Loader::factory($config['type'], '\\think\\db\\connector\\', $config); + } + + return self::$instance[$name]; + } + + /** + * 获取当前连接器类对应的Builder类 + * @access public + * @return string + */ + public function getBuilderClass() + { + if (!empty($this->builderClassName)) { + return $this->builderClassName; + } + + return $this->getConfig('builder') ?: '\\think\\db\\builder\\' . ucfirst($this->getConfig('type')); + } + + /** + * 设置当前的数据库Builder对象 + * @access protected + * @param Builder $builder + * @return void + */ + protected function setBuilder(Builder $builder) + { + $this->builder = $builder; + + return $this; + } + + /** + * 获取当前的builder实例对象 + * @access public + * @return Builder + */ + public function getBuilder() + { + return $this->builder; + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + abstract protected function parseDsn($config); + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + abstract public function getFields($tableName); + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + abstract public function getTables($dbName); + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + abstract protected function getExplain($sql); + + /** + * 对返数据表字段信息进行大小写转换出来 + * @access public + * @param array $info 字段信息 + * @return array + */ + public function fieldCase($info) + { + // 字段大小写转换 + switch ($this->attrCase) { + case PDO::CASE_LOWER: + $info = array_change_key_case($info); + break; + case PDO::CASE_UPPER: + $info = array_change_key_case($info, CASE_UPPER); + break; + case PDO::CASE_NATURAL: + default: + // 不做转换 + } + + return $info; + } + + /** + * 获取字段绑定类型 + * @access public + * @param string $type 字段类型 + * @return integer + */ + public function getFieldBindType($type) + { + if (0 === strpos($type, 'set') || 0 === strpos($type, 'enum')) { + $bind = PDO::PARAM_STR; + } elseif (preg_match('/(double|float|decimal|real|numeric)/is', $type)) { + $bind = self::PARAM_FLOAT; + } elseif (preg_match('/(int|serial|bit)/is', $type)) { + $bind = PDO::PARAM_INT; + } elseif (preg_match('/bool/is', $type)) { + $bind = PDO::PARAM_BOOL; + } else { + $bind = PDO::PARAM_STR; + } + + return $bind; + } + + /** + * 将SQL语句中的__TABLE_NAME__字符串替换成带前缀的表名(小写) + * @access public + * @param string $sql sql语句 + * @return string + */ + public function parseSqlTable($sql) + { + if (false !== strpos($sql, '__')) { + $sql = preg_replace_callback("/__([A-Z0-9_-]+)__/sU", function ($match) { + return $this->getConfig('prefix') . strtolower($match[1]); + }, $sql); + } + + return $sql; + } + + /** + * 获取数据表信息 + * @access public + * @param mixed $tableName 数据表名 留空自动获取 + * @param string $fetch 获取信息类型 包括 fields type bind pk + * @return mixed + */ + public function getTableInfo($tableName, $fetch = '') + { + if (is_array($tableName)) { + $tableName = key($tableName) ?: current($tableName); + } + + if (strpos($tableName, ',')) { + // 多表不获取字段信息 + return false; + } else { + $tableName = $this->parseSqlTable($tableName); + } + + // 修正子查询作为表名的问题 + if (strpos($tableName, ')')) { + return []; + } + + list($tableName) = explode(' ', $tableName); + + if (false === strpos($tableName, '.')) { + $schema = $this->getConfig('database') . '.' . $tableName; + } else { + $schema = $tableName; + } + + if (!isset(self::$info[$schema])) { + // 读取缓存 + $cacheFile = Container::get('app')->getRuntimePath() . 'schema' . DIRECTORY_SEPARATOR . $schema . '.php'; + + if (!$this->config['debug'] && is_file($cacheFile)) { + $info = include $cacheFile; + } else { + $info = $this->getFields($tableName); + } + + $fields = array_keys($info); + $bind = $type = []; + + foreach ($info as $key => $val) { + // 记录字段类型 + $type[$key] = $val['type']; + $bind[$key] = $this->getFieldBindType($val['type']); + + if (!empty($val['primary'])) { + $pk[] = $key; + } + } + + if (isset($pk)) { + // 设置主键 + $pk = count($pk) > 1 ? $pk : $pk[0]; + } else { + $pk = null; + } + + self::$info[$schema] = ['fields' => $fields, 'type' => $type, 'bind' => $bind, 'pk' => $pk]; + } + + return $fetch ? self::$info[$schema][$fetch] : self::$info[$schema]; + } + + /** + * 获取数据表的主键 + * @access public + * @param string $tableName 数据表名 + * @return string|array + */ + public function getPk($tableName) + { + return $this->getTableInfo($tableName, 'pk'); + } + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName) + { + return $this->getTableInfo($tableName, 'fields'); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName, $field = null) + { + $result = $this->getTableInfo($tableName, 'type'); + + if ($field && isset($result[$field])) { + return $result[$field]; + } + + return $result; + } + + /** + * 获取数据表绑定信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getFieldsBind($tableName) + { + return $this->getTableInfo($tableName, 'bind'); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $config 配置名称 + * @return mixed + */ + public function getConfig($config = '') + { + return $config ? $this->config[$config] : $this->config; + } + + /** + * 设置数据库的配置参数 + * @access public + * @param string|array $config 配置名称 + * @param mixed $value 配置值 + * @return void + */ + public function setConfig($config, $value = '') + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } else { + $this->config[$config] = $value; + } + } + + /** + * 连接数据库方法 + * @access public + * @param array $config 连接参数 + * @param integer $linkNum 连接序号 + * @param array|bool $autoConnection 是否自动连接主数据库(用于分布式) + * @return PDO + * @throws Exception + */ + public function connect(array $config = [], $linkNum = 0, $autoConnection = false) + { + if (isset($this->links[$linkNum])) { + return $this->links[$linkNum]; + } + + if (!$config) { + $config = $this->config; + } else { + $config = array_merge($this->config, $config); + } + + // 连接参数 + if (isset($config['params']) && is_array($config['params'])) { + $params = $config['params'] + $this->params; + } else { + $params = $this->params; + } + + // 记录当前字段属性大小写设置 + $this->attrCase = $params[PDO::ATTR_CASE]; + + if (!empty($config['break_match_str'])) { + $this->breakMatchStr = array_merge($this->breakMatchStr, (array) $config['break_match_str']); + } + + try { + if (empty($config['dsn'])) { + $config['dsn'] = $this->parseDsn($config); + } + + if ($config['debug']) { + $startTime = microtime(true); + } + + $this->links[$linkNum] = new PDO($config['dsn'], $config['username'], $config['password'], $params); + + if ($config['debug']) { + // 记录数据库连接信息 + $this->log('[ DB ] CONNECT:[ UseTime:' . number_format(microtime(true) - $startTime, 6) . 's ] ' . $config['dsn']); + } + + return $this->links[$linkNum]; + } catch (\PDOException $e) { + if ($autoConnection) { + $this->log($e->getMessage(), 'error'); + return $this->connect($autoConnection, $linkNum); + } else { + throw $e; + } + } + } + + /** + * 释放查询结果 + * @access public + */ + public function free() + { + $this->PDOStatement = null; + } + + /** + * 获取PDO对象 + * @access public + * @return \PDO|false + */ + public function getPdo() + { + if (!$this->linkID) { + return false; + } + + return $this->linkID; + } + + /** + * 执行查询 使用生成器返回数据 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param Model $model 模型对象实例 + * @param array $condition 查询条件 + * @param mixed $relation 关联查询 + * @return \Generator + */ + public function getCursor($sql, $bind = [], $master = false, $model = null, $condition = null, $relation = null) + { + $this->initConnect($master); + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$queryTimes++; + + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', $master); + + // 返回结果集 + while ($result = $this->PDOStatement->fetch($this->fetchType)) { + if ($model) { + $instance = $model->newInstance($result, $condition); + + if ($relation) { + $instance->relationQuery($relation); + } + + yield $instance; + } else { + yield $result; + } + } + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param bool $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return array + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + $this->initConnect($master); + + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$queryTimes++; + + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行查询 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', $master); + + // 返回结果集 + return $this->getResult($pdo, $procedure); + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->query($sql, $bind, $master, $pdo); + } + + throw $e; + } + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param Query $query 查询对象 + * @return int + * @throws BindParamException + * @throws \PDOException + * @throws \Exception + * @throws \Throwable + */ + public function execute($sql, $bind = [], Query $query = null) + { + $this->initConnect(true); + + if (!$this->linkID) { + return false; + } + + // 记录SQL语句 + $this->queryStr = $sql; + + $this->bind = $bind; + + Db::$executeTimes++; + try { + // 调试开始 + $this->debug(true); + + // 预处理 + $this->PDOStatement = $this->linkID->prepare($sql); + + // 是否为存储过程调用 + $procedure = in_array(strtolower(substr(trim($sql), 0, 4)), ['call', 'exec']); + + // 参数绑定 + if ($procedure) { + $this->bindParam($bind); + } else { + $this->bindValue($bind); + } + + // 执行语句 + $this->PDOStatement->execute(); + + // 调试结束 + $this->debug(false, '', true); + + if ($query && !empty($this->config['deploy']) && !empty($this->config['read_master'])) { + $query->readMaster(); + } + + $this->numRows = $this->PDOStatement->rowCount(); + + return $this->numRows; + } catch (\PDOException $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + + throw new PDOException($e, $this->config, $this->getLastsql()); + } catch (\Throwable $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + + throw $e; + } catch (\Exception $e) { + if ($this->isBreak($e)) { + return $this->close()->execute($sql, $bind, $query); + } + + throw $e; + } + } + + /** + * 查找单条记录 + * @access public + * @param Query $query 查询对象 + * @return array|null|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + + $data = $options['data']; + $query->setOption('limit', 1); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + if (is_string($cache['key'])) { + $key = $cache['key']; + } else { + $key = $this->getCacheKey($query, $data); + } + + $result = Container::get('cache')->get($key); + + if (false !== $result) { + return $result; + } + } + + if (is_string($pk) && !is_array($data)) { + if (isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + } else { + $item[$pk] = $data; + } + $data = $item; + } + + $query->setOption('data', $data); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 事件回调 + $result = $query->trigger('before_find'); + + if (!$result) { + // 执行查询 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + + $result = isset($resultSet[0]) ? $resultSet[0] : null; + } + + if (isset($cache) && $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + + return $result; + } + + /** + * 使用游标查询记录 + * @access public + * @param Query $query 查询对象 + * @return \Generator + */ + public function cursor(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + $condition = isset($options['where']['AND']) ? $options['where']['AND'] : null; + $relation = isset($options['relaltion']) ? $options['relation'] : null; + + // 执行查询操作 + return $this->getCursor($sql, $bind, $options['master'], $query->getModel(), $condition, $relation); + } + + /** + * 查找记录 + * @access public + * @param Query $query 查询对象 + * @return array|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $resultSet = $this->getCacheData($query, $options['cache'], null, $key); + + if (false !== $resultSet) { + return $resultSet; + } + } + + // 生成查询SQL + $sql = $this->builder->select($query); + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + $resultSet = $query->trigger('before_select'); + + if (!$resultSet) { + // 执行查询操作 + $resultSet = $this->query($sql, $bind, $options['master'], $options['fetch_pdo']); + + if ($resultSet instanceof \PDOStatement) { + // 返回PDOStatement对象 + return $resultSet; + } + } + + if (!empty($options['cache']) && false !== $resultSet) { + // 缓存数据集 + $this->cacheData($key, $resultSet, $options['cache']); + } + + return $resultSet; + } + + /** + * 插入记录 + * @access public + * @param Query $query 查询对象 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(Query $query, $replace = false, $getLastInsID = false, $sequence = null) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成SQL语句 + $sql = $this->builder->insert($query, $replace); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); + + if ($result) { + $sequence = $sequence ?: (isset($options['sequence']) ? $options['sequence'] : null); + $lastInsId = $this->getLastInsID($sequence); + + $data = $options['data']; + + if ($lastInsId) { + $pk = $query->getPk($options); + if (is_string($pk)) { + $data[$pk] = $lastInsId; + } + } + + $query->setOption('data', $data); + + $query->trigger('after_insert'); + + if ($getLastInsID) { + return $lastInsId; + } + } + + return $result; + } + + /** + * 批量插入记录 + * @access public + * @param Query $query 查询对象 + * @param mixed $dataSet 数据集 + * @param bool $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + * @throws \Exception + * @throws \Throwable + */ + public function insertAll(Query $query, $dataSet = [], $replace = false, $limit = null) + { + if (!is_array(reset($dataSet))) { + return false; + } + + $options = $query->getOptions(); + + if ($limit) { + // 分批写入 自动启动事务支持 + $this->startTrans(); + + try { + $array = array_chunk($dataSet, $limit, true); + $count = 0; + + foreach ($array as $item) { + $sql = $this->builder->insertAll($query, $item, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + $fetchSql[] = $this->getRealSql($sql, $bind); + } else { + $count += $this->execute($sql, $bind, $query); + } + } + + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + + return isset($fetchSql) ? implode(';', $fetchSql) : $count; + } + + $sql = $this->builder->insertAll($query, $dataSet, $replace); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + return $this->getRealSql($sql, $bind); + } + + return $this->execute($sql, $bind, $query); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param Query $query 查询对象 + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert(Query $query, $fields, $table) + { + // 分析查询表达式 + $options = $query->getOptions(); + + $table = $this->parseSqlTable($table); + + $sql = $this->builder->selectInsert($query, $fields, $table); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + return $this->getRealSql($sql, $bind); + } + + return $this->execute($sql, $bind, $query); + } + + /** + * 更新记录 + * @access public + * @param Query $query 查询对象 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(Query $query) + { + $options = $query->getOptions(); + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } + + $pk = $query->getPk($options); + $data = $options['data']; + + if (empty($options['where'])) { + // 如果存在主键数据 则自动作为更新条件 + if (is_string($pk) && isset($data[$pk])) { + $where[$pk] = [$pk, '=', $data[$pk]]; + if (!isset($key)) { + $key = $this->getCacheKey($query, $data[$pk]); + } + unset($data[$pk]); + } elseif (is_array($pk)) { + // 增加复合主键支持 + foreach ($pk as $field) { + if (isset($data[$field])) { + $where[$field] = [$field, '=', $data[$field]]; + } else { + // 如果缺少复合主键数据则不执行 + throw new Exception('miss complex primary data'); + } + unset($data[$field]); + } + } + + if (!isset($where)) { + // 如果没有任何更新条件则不执行 + throw new Exception('miss update condition'); + } else { + $options['where']['AND'] = $where; + $query->setOption('where', ['AND' => $where]); + } + } elseif (!isset($key) && is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } + } + + // 更新数据 + $query->setOption('data', $data); + + // 生成UPDATE SQL语句 + $sql = $this->builder->update($query); + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = '' == $sql ? 0 : $this->execute($sql, $bind, $query); + + if ($result) { + if (is_string($pk) && isset($where[$pk])) { + $data[$pk] = $where[$pk]; + } elseif (is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $data[$pk] = $val; + } + + $query->setOption('data', $data); + $query->trigger('after_update'); + } + + return $result; + } + + /** + * 删除记录 + * @access public + * @param Query $query 查询对象 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + $pk = $query->getPk($options); + $data = $options['data']; + + if (isset($options['cache']) && is_string($options['cache']['key'])) { + $key = $options['cache']['key']; + } elseif (!is_null($data) && true !== $data && !is_array($data)) { + $key = $this->getCacheKey($query, $data); + } elseif (is_string($pk) && isset($options['where']['AND'])) { + foreach ($options['where']['AND'] as $val) { + if (is_array($val) && $val[0] == $pk) { + $key = $this->getCacheKey($query, $val); + } + } + } + + if (true !== $data && empty($options['where'])) { + // 如果条件为空 不进行删除操作 除非设置 1=1 + throw new Exception('delete without condition'); + } + + // 生成删除SQL语句 + $sql = $this->builder->delete($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 检测缓存 + $cache = Container::get('cache'); + + if (isset($key) && $cache->get($key)) { + // 删除缓存 + $cache->rm($key); + } elseif (!empty($options['cache']['tag'])) { + $cache->clear($options['cache']['tag']); + } + + // 执行操作 + $result = $this->execute($sql, $bind, $query); + + if ($result) { + if (!is_array($data) && is_string($pk) && isset($key) && strpos($key, '|')) { + list($a, $val) = explode('|', $key); + $item[$pk] = $val; + $data = $item; + } + + $options['data'] = $data; + + $query->trigger('after_delete'); + } + + return $result; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 + * @param bool $default 默认值 + * @return mixed + */ + public function value(Query $query, $field, $default = null) + { + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $key); + + if (false !== $result) { + return $result; + } + } + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + $query->setOption('limit', 1); + + // 生成查询SQL + $sql = $this->builder->select($query); + + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + $query->removeOption('limit'); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + $result = $pdo->fetchColumn(); + + if (isset($cache) && false !== $result) { + // 缓存数据 + $this->cacheData($key, $result, $cache); + } + + return false !== $result ? $result : $default; + } + + /** + * 得到某个字段的值 + * @access public + * @param Query $query 查询对象 + * @param string $aggregate 聚合方法 + * @param mixed $field 字段名 + * @return mixed + */ + public function aggregate(Query $query, $aggregate, $field) + { + if (is_string($field) && 0 === stripos($field, 'DISTINCT ')) { + list($distinct, $field) = explode(' ', $field); + } + + $field = $aggregate . '(' . (!empty($distinct) ? 'DISTINCT ' : '') . $this->builder->parseKey($query, $field, true) . ') AS tp_' . strtolower($aggregate); + + return $this->value($query, $field, 0); + } + + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + $result = $this->getCacheData($query, $cache, null, $guid); + + if (false !== $result) { + return $result; + } + } + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = ['*']; + } elseif (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + if ($key && ['*'] != $field) { + array_unshift($field, $key); + $field = array_unique($field); + } + + $query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($query); + + // 还原field参数 + if (isset($options['field'])) { + $query->setOption('field', $options['field']); + } else { + $query->removeOption('field'); + } + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if (['*'] == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (2 == $count) { + $column = $key2; + } elseif (1 == $count) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + + return $result; + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function pdo(Query $query) + { + // 分析查询表达式 + $options = $query->getOptions(); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + return $this->query($sql, $bind, $options['master'], true); + } + + /** + * 根据参数绑定组装最终的SQL语句 便于调试 + * @access public + * @param string $sql 带参数绑定的sql语句 + * @param array $bind 参数绑定列表 + * @return string + */ + public function getRealSql($sql, array $bind = []) + { + if (is_array($sql)) { + $sql = implode(';', $sql); + } + + foreach ($bind as $key => $val) { + $value = is_array($val) ? $val[0] : $val; + $type = is_array($val) ? $val[1] : PDO::PARAM_STR; + + if (self::PARAM_FLOAT == $type) { + $value = (float) $value; + } elseif (PDO::PARAM_STR == $type) { + $value = '\'' . addslashes($value) . '\''; + } elseif (PDO::PARAM_INT == $type && '' === $value) { + $value = 0; + } + + // 判断占位符 + $sql = is_numeric($key) ? + substr_replace($sql, $value, strpos($sql, '?'), 1) : + substr_replace($sql, $value, strpos($sql, ':' . $key), strlen(':' . $key)); + } + + return rtrim($sql); + } + + /** + * 参数绑定 + * 支持 ['name'=>'value','id'=>123] 对应命名占位符 + * 或者 ['value',123] 对应问号占位符 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindValue(array $bind = []) + { + foreach ($bind as $key => $val) { + // 占位符 + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + if (PDO::PARAM_INT == $val[1] && '' === $val[0]) { + $val[0] = 0; + } elseif (self::PARAM_FLOAT == $val[1]) { + $val[0] = (float) $val[0]; + $val[1] = PDO::PARAM_STR; + } + + $result = $this->PDOStatement->bindValue($param, $val[0], $val[1]); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 存储过程的输入输出参数绑定 + * @access public + * @param array $bind 要绑定的参数列表 + * @return void + * @throws BindParamException + */ + protected function bindParam($bind) + { + foreach ($bind as $key => $val) { + $param = is_int($key) ? $key + 1 : ':' . $key; + + if (is_array($val)) { + array_unshift($val, $param); + $result = call_user_func_array([$this->PDOStatement, 'bindParam'], $val); + } else { + $result = $this->PDOStatement->bindValue($param, $val); + } + + if (!$result) { + $param = array_shift($val); + + throw new BindParamException( + "Error occurred when binding parameters '{$param}'", + $this->config, + $this->getLastsql(), + $bind + ); + } + } + } + + /** + * 获得数据集数组 + * @access protected + * @param bool $pdo 是否返回PDOStatement + * @param bool $procedure 是否存储过程 + * @return array + */ + protected function getResult($pdo = false, $procedure = false) + { + if ($pdo) { + // 返回PDOStatement对象处理 + return $this->PDOStatement; + } + + if ($procedure) { + // 存储过程返回结果 + return $this->procedure(); + } + + $result = $this->PDOStatement->fetchAll($this->fetchType); + + $this->numRows = count($result); + + return $result; + } + + /** + * 获得存储过程数据集 + * @access protected + * @return array + */ + protected function procedure() + { + $item = []; + + do { + $result = $this->getResult(); + if ($result) { + $item[] = $result; + } + } while ($this->PDOStatement->nextRowset()); + + $this->numRows = count($item); + + return $item; + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transaction($callback) + { + $this->startTrans(); + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + $this->commit(); + return $result; + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } catch (\Throwable $e) { + $this->rollback(); + throw $e; + } + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa($xid) + {} + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + {} + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + {} + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + {} + + /** + * 启动事务 + * @access public + * @return void + * @throws \PDOException + * @throws \Exception + */ + public function startTrans() + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + ++$this->transTimes; + + try { + if (1 == $this->transTimes) { + $this->linkID->beginTransaction(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepoint('trans' . $this->transTimes) + ); + } + } catch (\Exception $e) { + if ($this->isBreak($e)) { + --$this->transTimes; + return $this->close()->startTrans(); + } + throw $e; + } + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->commit(); + } + + --$this->transTimes; + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->initConnect(true); + + if (1 == $this->transTimes) { + $this->linkID->rollBack(); + } elseif ($this->transTimes > 1 && $this->supportSavepoint()) { + $this->linkID->exec( + $this->parseSavepointRollBack('trans' . $this->transTimes) + ); + } + + $this->transTimes = max(0, $this->transTimes - 1); + } + + /** + * 是否支持事务嵌套 + * @return bool + */ + protected function supportSavepoint() + { + return false; + } + + /** + * 生成定义保存点的SQL + * @access protected + * @param $name + * @return string + */ + protected function parseSavepoint($name) + { + return 'SAVEPOINT ' . $name; + } + + /** + * 生成回滚到保存点的SQL + * @access protected + * @param $name + * @return string + */ + protected function parseSavepointRollBack($name) + { + return 'ROLLBACK TO SAVEPOINT ' . $name; + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sqlArray SQL批处理指令 + * @param array $bind 参数绑定 + * @return boolean + */ + public function batchQuery($sqlArray = [], $bind = []) + { + if (!is_array($sqlArray)) { + return false; + } + + // 自动启动事务支持 + $this->startTrans(); + + try { + foreach ($sqlArray as $sql) { + $this->execute($sql, $bind); + } + // 提交事务 + $this->commit(); + } catch (\Exception $e) { + $this->rollback(); + throw $e; + } + + return true; + } + + /** + * 获得查询次数 + * @access public + * @param boolean $execute 是否包含所有查询 + * @return integer + */ + public function getQueryTimes($execute = false) + { + return $execute ? Db::$queryTimes + Db::$executeTimes : Db::$queryTimes; + } + + /** + * 获得执行次数 + * @access public + * @return integer + */ + public function getExecuteTimes() + { + return Db::$executeTimes; + } + + /** + * 关闭数据库(或者重新连接) + * @access public + * @return $this + */ + public function close() + { + $this->linkID = null; + $this->linkWrite = null; + $this->linkRead = null; + $this->links = []; + + // 释放查询 + $this->free(); + + return $this; + } + + /** + * 是否断线 + * @access protected + * @param \PDOException|\Exception $e 异常对象 + * @return bool + */ + protected function isBreak($e) + { + if (!$this->config['break_reconnect']) { + return false; + } + + $error = $e->getMessage(); + + foreach ($this->breakMatchStr as $msg) { + if (false !== stripos($error, $msg)) { + return true; + } + } + return false; + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->getRealSql($this->queryStr, $this->bind); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->linkID->lastInsertId($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->numRows; + } + + /** + * 获取最近的错误信息 + * @access public + * @return string + */ + public function getError() + { + if ($this->PDOStatement) { + $error = $this->PDOStatement->errorInfo(); + $error = $error[1] . ':' . $error[2]; + } else { + $error = ''; + } + + if ('' != $this->queryStr) { + $error .= "\n [ SQL语句 ] : " . $this->getLastsql(); + } + + return $error; + } + + /** + * 数据库调试 记录当前SQL及分析性能 + * @access protected + * @param boolean $start 调试开始标记 true 开始 false 结束 + * @param string $sql 执行的SQL语句 留空自动获取 + * @param bool $master 主从标记 + * @return void + */ + protected function debug($start, $sql = '', $master = false) + { + if (!empty($this->config['debug'])) { + // 开启数据库调试模式 + $debug = Container::get('debug'); + + if ($start) { + $debug->remark('queryStartTime', 'time'); + } else { + // 记录操作结束时间 + $debug->remark('queryEndTime', 'time'); + $runtime = $debug->getRangeTime('queryStartTime', 'queryEndTime'); + $sql = $sql ?: $this->getLastsql(); + $result = []; + + // SQL性能分析 + if ($this->config['sql_explain'] && 0 === stripos(trim($sql), 'select')) { + $result = $this->getExplain($sql); + } + + // SQL监听 + $this->triggerSql($sql, $runtime, $result, $master); + } + } + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + self::$event[] = $callback; + } + + /** + * 触发SQL事件 + * @access protected + * @param string $sql SQL语句 + * @param float $runtime SQL运行时间 + * @param mixed $explain SQL分析 + * @param bool $master 主从标记 + * @return void + */ + protected function triggerSql($sql, $runtime, $explain = [], $master = false) + { + if (!empty(self::$event)) { + foreach (self::$event as $callback) { + if (is_callable($callback)) { + call_user_func_array($callback, [$sql, $runtime, $explain, $master]); + } + } + } else { + if ($this->config['deploy']) { + // 分布式记录当前操作的主从 + $master = $master ? 'master|' : 'slave|'; + } else { + $master = ''; + } + + // 未注册监听则记录到日志中 + $this->log('[ SQL ] ' . $sql . ' [ ' . $master . 'RunTime:' . $runtime . 's ]'); + + if (!empty($explain)) { + $this->log('[ EXPLAIN : ' . var_export($explain, true) . ' ]'); + } + } + } + + public function log($log, $type = 'sql') + { + $this->config['debug'] && Container::get('log')->record($log, $type); + } + + /** + * 初始化数据库连接 + * @access protected + * @param boolean $master 是否主服务器 + * @return void + */ + protected function initConnect($master = true) + { + if (!empty($this->config['deploy'])) { + // 采用分布式数据库 + if ($master || $this->transTimes) { + if (!$this->linkWrite) { + $this->linkWrite = $this->multiConnect(true); + } + + $this->linkID = $this->linkWrite; + } else { + if (!$this->linkRead) { + $this->linkRead = $this->multiConnect(false); + } + + $this->linkID = $this->linkRead; + } + } elseif (!$this->linkID) { + // 默认单数据库 + $this->linkID = $this->connect(); + } + } + + /** + * 连接分布式服务器 + * @access protected + * @param boolean $master 主服务器 + * @return PDO + */ + protected function multiConnect($master = false) + { + $_config = []; + + // 分布式数据库配置解析 + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $_config[$name] = is_string($this->config[$name]) ? explode(',', $this->config[$name]) : $this->config[$name]; + } + + // 主服务器序号 + $m = floor(mt_rand(0, $this->config['master_num'] - 1)); + + if ($this->config['rw_separate']) { + // 主从式采用读写分离 + if ($master) // 主服务器写入 + { + $r = $m; + } elseif (is_numeric($this->config['slave_no'])) { + // 指定服务器读 + $r = $this->config['slave_no']; + } else { + // 读操作连接从服务器 每次随机连接的数据库 + $r = floor(mt_rand($this->config['master_num'], count($_config['hostname']) - 1)); + } + } else { + // 读写操作不区分服务器 每次随机连接的数据库 + $r = floor(mt_rand(0, count($_config['hostname']) - 1)); + } + $dbMaster = false; + + if ($m != $r) { + $dbMaster = []; + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbMaster[$name] = isset($_config[$name][$m]) ? $_config[$name][$m] : $_config[$name][0]; + } + } + + $dbConfig = []; + + foreach (['username', 'password', 'hostname', 'hostport', 'database', 'dsn', 'charset'] as $name) { + $dbConfig[$name] = isset($_config[$name][$r]) ? $_config[$name][$r] : $_config[$name][0]; + } + + return $this->connect($dbConfig, $r, $r == $m ? false : $dbMaster); + } + + /** + * 析构方法 + * @access public + */ + public function __destruct() + { + // 关闭连接 + $this->close(); + } + + /** + * 缓存数据 + * @access protected + * @param string $key 缓存标识 + * @param mixed $data 缓存数据 + * @param array $config 缓存参数 + */ + protected function cacheData($key, $data, $config = []) + { + $cache = Container::get('cache'); + + if (isset($config['tag'])) { + $cache->tag($config['tag'])->set($key, $data, $config['expire']); + } else { + $cache->set($key, $data, $config['expire']); + } + } + + /** + * 获取缓存数据 + * @access protected + * @param Query $query 查询对象 + * @param mixed $cache 缓存设置 + * @param array $options 缓存 + * @return mixed + */ + protected function getCacheData(Query $query, $cache, $data, &$key = null) + { + // 判断查询缓存 + $key = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $data); + + return Container::get('cache')->get($key); + } + + /** + * 生成缓存标识 + * @access protected + * @param Query $query 查询对象 + * @param mixed $value 缓存数据 + * @return string + */ + protected function getCacheKey(Query $query, $value) + { + if (is_scalar($value)) { + $data = $value; + } elseif (is_array($value) && isset($value[1], $value[2]) && in_array($value[1], ['=', 'eq'], true) && is_scalar($value[2])) { + $data = $value[2]; + } + + $prefix = 'think:' . $this->getConfig('database') . '.'; + + if (isset($data)) { + return $prefix . $query->getTable() . '|' . $data; + } + + try { + return md5($prefix . serialize($query->getOptions()) . serialize($query->getBind(false))); + } catch (\Exception $e) { + throw new Exception('closure not support cache(true)'); + } + } + +} diff --git a/thinkphp/library/think/db/Expression.php b/thinkphp/library/think/db/Expression.php new file mode 100644 index 0000000..f1b92ab --- /dev/null +++ b/thinkphp/library/think/db/Expression.php @@ -0,0 +1,48 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +class Expression +{ + /** + * 查询表达式 + * + * @var string + */ + protected $value; + + /** + * 创建一个查询表达式 + * + * @param string $value + * @return void + */ + public function __construct($value) + { + $this->value = $value; + } + + /** + * 获取表达式 + * + * @return string + */ + public function getValue() + { + return $this->value; + } + + public function __toString() + { + return (string) $this->value; + } +} diff --git a/thinkphp/library/think/db/Query.php b/thinkphp/library/think/db/Query.php new file mode 100644 index 0000000..0048332 --- /dev/null +++ b/thinkphp/library/think/db/Query.php @@ -0,0 +1,3737 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use PDO; +use think\Collection; +use think\Container; +use think\Db; +use think\db\exception\BindParamException; +use think\db\exception\DataNotFoundException; +use think\db\exception\ModelNotFoundException; +use think\Exception; +use think\exception\DbException; +use think\exception\PDOException; +use think\Loader; +use think\Model; +use think\model\Collection as ModelCollection; +use think\model\Relation; +use think\model\relation\OneToOne; +use think\Paginator; + +class Query +{ + /** + * 当前数据库连接对象 + * @var Connection + */ + protected $connection; + + /** + * 当前模型对象 + * @var Model + */ + protected $model; + + /** + * 当前数据表名称(不含前缀) + * @var string + */ + protected $name = ''; + + /** + * 当前数据表主键 + * @var string|array + */ + protected $pk; + + /** + * 当前数据表前缀 + * @var string + */ + protected $prefix = ''; + + /** + * 当前查询参数 + * @var array + */ + protected $options = []; + + /** + * 当前参数绑定 + * @var array + */ + protected $bind = []; + + /** + * 事件回调 + * @var array + */ + private static $event = []; + + /** + * 扩展查询方法 + * @var array + */ + private static $extend = []; + + /** + * 读取主库的表 + * @var array + */ + protected static $readMaster = []; + + /** + * 日期查询表达式 + * @var array + */ + protected $timeRule = [ + 'today' => ['today', 'tomorrow'], + 'yesterday' => ['yesterday', 'today'], + 'week' => ['this week 00:00:00', 'next week 00:00:00'], + 'last week' => ['last week 00:00:00', 'this week 00:00:00'], + 'month' => ['first Day of this month 00:00:00', 'first Day of next month 00:00:00'], + 'last month' => ['first Day of last month 00:00:00', 'first Day of this month 00:00:00'], + 'year' => ['this year 1/1', 'next year 1/1'], + 'last year' => ['last year 1/1', 'this year 1/1'], + ]; + + /** + * 日期查询快捷定义 + * @var array + */ + protected $timeExp = ['d' => 'today', 'w' => 'week', 'm' => 'month', 'y' => 'year']; + + /** + * 架构函数 + * @access public + */ + public function __construct(Connection $connection = null) + { + if (is_null($connection)) { + $this->connection = Db::connect(); + } else { + $this->connection = $connection; + } + + $this->prefix = $this->connection->getConfig('prefix'); + } + + /** + * 创建一个新的查询对象 + * @access public + * @return Query + */ + public function newQuery() + { + return new static($this->connection); + } + + /** + * 利用__call方法实现一些特殊的Model方法 + * @access public + * @param string $method 方法名称 + * @param array $args 调用参数 + * @return mixed + * @throws DbException + * @throws Exception + */ + public function __call($method, $args) + { + if (isset(self::$extend[strtolower($method)])) { + // 调用扩展查询方法 + array_unshift($args, $this); + + return Container::getInstance() + ->invoke(self::$extend[strtolower($method)], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'getby') { + // 根据某个字段获取记录 + $field = Loader::parseName(substr($method, 5)); + return $this->where($field, '=', $args[0])->find(); + } elseif (strtolower(substr($method, 0, 10)) == 'getfieldby') { + // 根据某个字段获取记录的某个值 + $name = Loader::parseName(substr($method, 10)); + return $this->where($name, '=', $args[0])->value($args[1]); + } elseif (strtolower(substr($method, 0, 7)) == 'whereor') { + $name = Loader::parseName(substr($method, 7)); + array_unshift($args, $name); + return call_user_func_array([$this, 'whereOr'], $args); + } elseif (strtolower(substr($method, 0, 5)) == 'where') { + $name = Loader::parseName(substr($method, 5)); + array_unshift($args, $name); + return call_user_func_array([$this, 'where'], $args); + } elseif ($this->model && method_exists($this->model, 'scope' . $method)) { + // 动态调用命名范围 + $method = 'scope' . $method; + array_unshift($args, $this); + + call_user_func_array([$this->model, $method], $args); + return $this; + } else { + throw new Exception('method not exist:' . ($this->model ? get_class($this->model) : static::class) . '->' . $method); + } + } + + /** + * 扩展查询方法 + * @access public + * @param string|array $method 查询方法名 + * @param callable $callback + * @return void + */ + public static function extend($method, $callback = null) + { + if (is_array($method)) { + foreach ($method as $key => $val) { + self::$extend[strtolower($key)] = $val; + } + } else { + self::$extend[strtolower($method)] = $callback; + } + } + + /** + * 设置当前的数据库Connection对象 + * @access public + * @param Connection $connection + * @return $this + */ + public function setConnection(Connection $connection) + { + $this->connection = $connection; + $this->prefix = $this->connection->getConfig('prefix'); + + return $this; + } + + /** + * 获取当前的数据库Connection对象 + * @access public + * @return Connection + */ + public function getConnection() + { + return $this->connection; + } + + /** + * 指定模型 + * @access public + * @param Model $model 模型对象实例 + * @return $this + */ + public function model(Model $model) + { + $this->model = $model; + return $this; + } + + /** + * 获取当前的模型对象 + * @access public + * @return Model|null + */ + public function getModel() + { + return $this->model ? $this->model->setQuery($this) : null; + } + + /** + * 设置从主库读取数据 + * @access public + * @param bool $all 是否所有表有效 + * @return $this + */ + public function readMaster($all = false) + { + $table = $all ? '*' : $this->getTable(); + + static::$readMaster[$table] = true; + + return $this; + } + + /** + * 指定当前数据表名(不含前缀) + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + return $this; + } + + /** + * 获取当前的数据表名称 + * @access public + * @return string + */ + public function getName() + { + return $this->name ?: $this->model->getName(); + } + + /** + * 得到当前或者指定名称的数据表 + * @access public + * @param string $name + * @return string + */ + public function getTable($name = '') + { + if (empty($name) && isset($this->options['table'])) { + return $this->options['table']; + } + + $name = $name ?: $this->name; + + return $this->prefix . Loader::parseName($name); + } + + /** + * 执行查询 返回数据集 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @param boolean $master 是否在主服务器读操作 + * @param bool $pdo 是否返回PDO对象 + * @return mixed + * @throws BindParamException + * @throws PDOException + */ + public function query($sql, $bind = [], $master = false, $pdo = false) + { + return $this->connection->query($sql, $bind, $master, $pdo); + } + + /** + * 执行语句 + * @access public + * @param string $sql sql指令 + * @param array $bind 参数绑定 + * @return int + * @throws BindParamException + * @throws PDOException + */ + public function execute($sql, $bind = []) + { + return $this->connection->execute($sql, $bind, $this); + } + + /** + * 监听SQL执行 + * @access public + * @param callable $callback 回调方法 + * @return void + */ + public function listen($callback) + { + $this->connection->listen($callback); + } + + /** + * 获取最近插入的ID + * @access public + * @param string $sequence 自增序列名 + * @return string + */ + public function getLastInsID($sequence = null) + { + return $this->connection->getLastInsID($sequence); + } + + /** + * 获取返回或者影响的记录数 + * @access public + * @return integer + */ + public function getNumRows() + { + return $this->connection->getNumRows(); + } + + /** + * 获取最近一次查询的sql语句 + * @access public + * @return string + */ + public function getLastSql() + { + return $this->connection->getLastSql(); + } + + /** + * 执行数据库Xa事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @param array $dbs 多个查询对象或者连接对象 + * @return mixed + * @throws PDOException + * @throws \Exception + * @throws \Throwable + */ + public function transactionXa($callback, array $dbs = []) + { + $xid = uniqid('xa'); + + if (empty($dbs)) { + $dbs[] = $this->getConnection(); + } + + foreach ($dbs as $key => $db) { + if ($db instanceof Query) { + $db = $db->getConnection(); + + $dbs[$key] = $db; + } + + $db->startTransXa($xid); + } + + try { + $result = null; + if (is_callable($callback)) { + $result = call_user_func_array($callback, [$this]); + } + + foreach ($dbs as $db) { + $db->prepareXa($xid); + } + + foreach ($dbs as $db) { + $db->commitXa($xid); + } + + return $result; + } catch (\Exception $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } catch (\Throwable $e) { + foreach ($dbs as $db) { + $db->rollbackXa($xid); + } + throw $e; + } + } + + /** + * 执行数据库事务 + * @access public + * @param callable $callback 数据操作方法回调 + * @return mixed + */ + public function transaction($callback) + { + return $this->connection->transaction($callback); + } + + /** + * 启动事务 + * @access public + * @return void + */ + public function startTrans() + { + $this->connection->startTrans(); + } + + /** + * 用于非自动提交状态下面的查询提交 + * @access public + * @return void + * @throws PDOException + */ + public function commit() + { + $this->connection->commit(); + } + + /** + * 事务回滚 + * @access public + * @return void + * @throws PDOException + */ + public function rollback() + { + $this->connection->rollback(); + } + + /** + * 批处理执行SQL语句 + * 批处理的指令都认为是execute操作 + * @access public + * @param array $sql SQL批处理指令 + * @return boolean + */ + public function batchQuery($sql = []) + { + return $this->connection->batchQuery($sql); + } + + /** + * 获取数据库的配置参数 + * @access public + * @param string $name 参数名称 + * @return mixed + */ + public function getConfig($name = '') + { + return $this->connection->getConfig($name); + } + + /** + * 获取数据表字段信息 + * @access public + * @param string $tableName 数据表名 + * @return array + */ + public function getTableFields($tableName = '') + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getTableFields($tableName); + } + + /** + * 获取数据表字段类型 + * @access public + * @param string $tableName 数据表名 + * @param string $field 字段名 + * @return array|string + */ + public function getFieldsType($tableName = '', $field = null) + { + if ('' == $tableName) { + $tableName = isset($this->options['table']) ? $this->options['table'] : $this->getTable(); + } + + return $this->connection->getFieldsType($tableName, $field); + } + + /** + * 得到分表的的数据表名 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return array + */ + public function getPartitionTableName($data, $field, $rule = []) + { + // 对数据表进行分区 + if ($field && isset($data[$field])) { + $value = $data[$field]; + $type = $rule['type']; + switch ($type) { + case 'id': + // 按照id范围分表 + $step = $rule['expr']; + $seq = floor($value / $step) + 1; + break; + case 'year': + // 按照年份分表 + if (!is_numeric($value)) { + $value = strtotime($value); + } + $seq = date('Y', $value) - $rule['expr'] + 1; + break; + case 'mod': + // 按照id的模数分表 + $seq = ($value % $rule['num']) + 1; + break; + case 'md5': + // 按照md5的序列分表 + $seq = (ord(substr(md5($value), 0, 1)) % $rule['num']) + 1; + break; + default: + if (function_exists($type)) { + // 支持指定函数哈希 + $seq = (ord(substr($type($value), 0, 1)) % $rule['num']) + 1; + } else { + // 按照字段的首字母的值分表 + $seq = (ord($value{0}) % $rule['num']) + 1; + } + } + return $this->getTable() . '_' . $seq; + } + // 当设置的分表字段不在查询条件或者数据中 + // 进行联合查询,必须设定 partition['num'] + $tableName = []; + for ($i = 0; $i < $rule['num']; $i++) { + $tableName[] = 'SELECT * FROM ' . $this->getTable() . '_' . ($i + 1); + } + + return ['( ' . implode(" UNION ", $tableName) . ' )' => $this->name]; + } + + /** + * 得到某个字段的值 + * @access public + * @param string $field 字段名 + * @param mixed $default 默认值 + * @return mixed + */ + public function value($field, $default = null) + { + $this->parseOptions(); + + return $this->connection->value($this, $field, $default); + } + + /** + * 得到某个列的数组 + * @access public + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column($field, $key = '') + { + $this->parseOptions(); + + return $this->connection->column($this, $field, $key); + } + + /** + * 聚合查询 + * @access public + * @param string $aggregate 聚合方法 + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function aggregate($aggregate, $field, $force = false) + { + $this->parseOptions(); + + $result = $this->connection->aggregate($this, $aggregate, $field); + + if (!empty($this->options['fetch_sql'])) { + return $result; + } elseif ($force) { + $result = (float) $result; + } + + // 查询完成后清空聚合字段信息 + $this->removeOption('field'); + + return $result; + } + + /** + * COUNT查询 + * @access public + * @param string $field 字段名 + * @return float|string + */ + public function count($field = '*') + { + if (!empty($this->options['group'])) { + // 支持GROUP + $options = $this->getOptions(); + $subSql = $this->options($options) + ->field('count(' . $field . ') AS think_count') + ->bind($this->bind) + ->buildSql(); + + $query = $this->newQuery()->table([$subSql => '_group_count_']); + + if (!empty($options['fetch_sql'])) { + $query->fetchSql(true); + } + + $count = $query->aggregate('COUNT', '*', true); + } else { + $count = $this->aggregate('COUNT', $field, true); + } + + return is_string($count) ? $count : (int) $count; + } + + /** + * SUM查询 + * @access public + * @param string $field 字段名 + * @return float + */ + public function sum($field) + { + return $this->aggregate('SUM', $field, true); + } + + /** + * MIN查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function min($field, $force = true) + { + return $this->aggregate('MIN', $field, $force); + } + + /** + * MAX查询 + * @access public + * @param string $field 字段名 + * @param bool $force 强制转为数字类型 + * @return mixed + */ + public function max($field, $force = true) + { + return $this->aggregate('MAX', $field, $force); + } + + /** + * AVG查询 + * @access public + * @param string $field 字段名 + * @return float + */ + public function avg($field) + { + return $this->aggregate('AVG', $field, true); + } + + /** + * 设置记录的某个字段值 + * 支持使用数据库字段和方法 + * @access public + * @param string|array $field 字段名 + * @param mixed $value 字段值 + * @return integer + */ + public function setField($field, $value = '') + { + if (is_array($field)) { + $data = $field; + } else { + $data[$field] = $value; + } + + return $this->update($data); + } + + /** + * 字段值(延迟)增长 + * @access public + * @param string $field 字段名 + * @param integer $step 增长值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setInc($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); + $step = $this->lazyWrite('inc', $guid, $step, $lazyTime); + + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + } + + return $this->setField($field, ['INC', $step]); + } + + /** + * 字段值(延迟)减少 + * @access public + * @param string $field 字段名 + * @param integer $step 减少值 + * @param integer $lazyTime 延时时间(s) + * @return integer|true + * @throws Exception + */ + public function setDec($field, $step = 1, $lazyTime = 0) + { + $condition = !empty($this->options['where']) ? $this->options['where'] : []; + + if (empty($condition)) { + // 没有条件不做任何更新 + throw new Exception('no data to update'); + } + + if ($lazyTime > 0) { + // 延迟写入 + $guid = md5($this->getTable() . '_' . $field . '_' . serialize($condition)); + $step = $this->lazyWrite('dec', $guid, $step, $lazyTime); + + if (false === $step) { + // 清空查询条件 + $this->options = []; + return true; + } + + $value = ['INC', $step]; + } else { + $value = ['DEC', $step]; + } + + return $this->setField($field, $value); + } + + /** + * 延时更新检查 返回false表示需要延时 + * 否则返回实际写入的数值 + * @access protected + * @param string $type 自增或者自减 + * @param string $guid 写入标识 + * @param integer $step 写入步进值 + * @param integer $lazyTime 延时时间(s) + * @return false|integer + */ + protected function lazyWrite($type, $guid, $step, $lazyTime) + { + $cache = Container::get('cache'); + + if (!$cache->has($guid . '_time')) { + // 计时开始 + $cache->set($guid . '_time', time(), 0); + $cache->$type($guid, $step); + } elseif (time() > $cache->get($guid . '_time') + $lazyTime) { + // 删除缓存 + $value = $cache->$type($guid, $step); + $cache->rm($guid); + $cache->rm($guid . '_time'); + return 0 === $value ? false : $value; + } else { + // 更新缓存 + $cache->$type($guid, $step); + } + + return false; + } + + /** + * 查询SQL组装 join + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @param string $type JOIN类型 + * @return $this + */ + public function join($join, $condition = null, $type = 'INNER') + { + if (empty($condition)) { + // 如果为组数,则循环调用join + foreach ($join as $key => $value) { + if (is_array($value) && 2 <= count($value)) { + $this->join($value[0], $value[1], isset($value[2]) ? $value[2] : $type); + } + } + } else { + $table = $this->getJoinTable($join); + + $this->options['join'][] = [$table, strtoupper($type), $condition]; + } + + return $this; + } + + /** + * LEFT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function leftJoin($join, $condition = null) + { + return $this->join($join, $condition, 'LEFT'); + } + + /** + * RIGHT JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function rightJoin($join, $condition = null) + { + return $this->join($join, $condition, 'RIGHT'); + } + + /** + * FULL JOIN + * @access public + * @param mixed $join 关联的表名 + * @param mixed $condition 条件 + * @return $this + */ + public function fullJoin($join, $condition = null) + { + return $this->join($join, $condition, 'FULL'); + } + + /** + * 获取Join表名及别名 支持 + * ['prefix_table或者子查询'=>'alias'] 'table alias' + * @access protected + * @param array|string $join + * @param string $alias + * @return string + */ + protected function getJoinTable($join, &$alias = null) + { + if (is_array($join)) { + $table = $join; + $alias = array_shift($join); + } else { + $join = trim($join); + + if (false !== strpos($join, '(')) { + // 使用子查询 + $table = $join; + } else { + $prefix = $this->prefix; + if (strpos($join, ' ')) { + // 使用别名 + list($table, $alias) = explode(' ', $join); + } else { + $table = $join; + if (false === strpos($join, '.') && 0 !== strpos($join, '__')) { + $alias = $join; + } + } + + if ($prefix && false === strpos($table, '.') && 0 !== strpos($table, $prefix) && 0 !== strpos($table, '__')) { + $table = $this->getTable($table); + } + } + + if (isset($alias) && $table != $alias) { + $table = [$table => $alias]; + } + } + + return $table; + } + + /** + * 查询SQL组装 union + * @access public + * @param mixed $union + * @param boolean $all + * @return $this + */ + public function union($union, $all = false) + { + $this->options['union']['type'] = $all ? 'UNION ALL' : 'UNION'; + + if (is_array($union)) { + $this->options['union'] = array_merge($this->options['union'], $union); + } else { + $this->options['union'][] = $union; + } + + return $this; + } + + /** + * 查询SQL组装 union all + * @access public + * @param mixed $union + * @return $this + */ + public function unionAll($union) + { + return $this->union($union, true); + } + + /** + * 指定查询字段 支持字段排除和指定数据表 + * @access public + * @param mixed $field + * @param boolean $except 是否排除 + * @param string $tableName 数据表名 + * @param string $prefix 字段前缀 + * @param string $alias 别名前缀 + * @return $this + */ + public function field($field, $except = false, $tableName = '', $prefix = '', $alias = '') + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['field'][] = $field; + return $this; + } + + if (is_string($field)) { + if (preg_match('/[\<\'\"\(]/', $field)) { + return $this->fieldRaw($field); + } + + $field = array_map('trim', explode(',', $field)); + } + + if (true === $field) { + // 获取全部字段 + $fields = $this->getTableFields($tableName); + $field = $fields ?: ['*']; + } elseif ($except) { + // 字段排除 + $fields = $this->getTableFields($tableName); + $field = $fields ? array_diff($fields, $field) : $field; + } + + if ($tableName) { + // 添加统一的前缀 + $prefix = $prefix ?: $tableName; + foreach ($field as $key => &$val) { + if (is_numeric($key) && $alias) { + $field[$prefix . '.' . $val] = $alias . $val; + unset($field[$key]); + } elseif (is_numeric($key)) { + $val = $prefix . '.' . $val; + } + } + } + + if (isset($this->options['field'])) { + $field = array_merge((array) $this->options['field'], $field); + } + + $this->options['field'] = array_unique($field); + + return $this; + } + + /** + * 表达式方式指定查询字段 + * @access public + * @param string $field 字段名 + * @return $this + */ + public function fieldRaw($field) + { + $this->options['field'][] = $this->raw($field); + + return $this; + } + + /** + * 设置数据 + * @access public + * @param mixed $field 字段名或者数据 + * @param mixed $value 字段值 + * @return $this + */ + public function data($field, $value = null) + { + if (is_array($field)) { + $this->options['data'] = isset($this->options['data']) ? array_merge($this->options['data'], $field) : $field; + } else { + $this->options['data'][$field] = $value; + } + + return $this; + } + + /** + * 字段值增长 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function inc($field, $step = 1, $op = 'INC') + { + $fields = is_string($field) ? explode(',', $field) : $field; + + foreach ($fields as $field => $val) { + if (is_numeric($field)) { + $field = $val; + } else { + $step = $val; + } + + $this->data($field, [$op, $step]); + } + + return $this; + } + + /** + * 字段值减少 + * @access public + * @param string|array $field 字段名 + * @param integer $step 增长值 + * @return $this + */ + public function dec($field, $step = 1) + { + return $this->inc($field, $step, 'DEC'); + } + + /** + * 使用表达式设置数据 + * @access public + * @param string $field 字段名 + * @param string $value 字段值 + * @return $this + */ + public function exp($field, $value) + { + $this->data($field, $this->raw($value)); + return $this; + } + + /** + * 使用表达式设置数据 + * @access public + * @param mixed $value 表达式 + * @return Expression + */ + public function raw($value) + { + return new Expression($value); + } + + /** + * 指定JOIN查询字段 + * @access public + * @param string|array $table 数据表 + * @param string|array $field 查询字段 + * @param mixed $on JOIN条件 + * @param string $type JOIN类型 + * @return $this + */ + public function view($join, $field = true, $on = null, $type = 'INNER') + { + $this->options['view'] = true; + + if (is_array($join) && key($join) === 0) { + foreach ($join as $key => $val) { + $this->view($val[0], $val[1], isset($val[2]) ? $val[2] : null, isset($val[3]) ? $val[3] : 'INNER'); + } + } else { + $fields = []; + $table = $this->getJoinTable($join, $alias); + + if (true === $field) { + $fields = $alias . '.*'; + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $fields[] = $alias . '.' . $val; + + $this->options['map'][$val] = $alias . '.' . $val; + } else { + if (preg_match('/[,=\.\'\"\(\s]/', $key)) { + $name = $key; + } else { + $name = $alias . '.' . $key; + } + + $fields[] = $name . ' AS ' . $val; + + $this->options['map'][$val] = $name; + } + } + } + + $this->field($fields); + + if ($on) { + $this->join($table, $on, $type); + } else { + $this->table($table); + } + } + + return $this; + } + + /** + * 设置分表规则 + * @access public + * @param array $data 操作的数据 + * @param string $field 分表依据的字段 + * @param array $rule 分表规则 + * @return $this + */ + public function partition($data, $field, $rule = []) + { + $this->options['table'] = $this->getPartitionTableName($data, $field, $rule); + + return $this; + } + + /** + * 指定AND查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function where($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('AND', $field, $op, $condition, $param); + } + + /** + * 指定OR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereOr($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('OR', $field, $op, $condition, $param); + } + + /** + * 指定XOR查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @return $this + */ + public function whereXor($field, $op = null, $condition = null) + { + $param = func_get_args(); + array_shift($param); + return $this->parseWhereExp('XOR', $field, $op, $condition, $param); + } + + /** + * 指定Null查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNull($field, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NULL', null, [], true); + } + + /** + * 指定NotNull查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotNull($field, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOTNULL', null, [], true); + } + + /** + * 指定Exists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExists($condition, $logic = 'AND') + { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'EXISTS', $condition]; + return $this; + } + + /** + * 指定NotExists查询条件 + * @access public + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotExists($condition, $logic = 'AND') + { + if (is_string($condition)) { + $condition = $this->raw($condition); + } + + $this->options['where'][strtoupper($logic)][] = ['', 'NOT EXISTS', $condition]; + return $this; + } + + /** + * 指定In查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereIn($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'IN', $condition, [], true); + } + + /** + * 指定NotIn查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotIn($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT IN', $condition, [], true); + } + + /** + * 指定Like查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereLike($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'LIKE', $condition, [], true); + } + + /** + * 指定NotLike查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotLike($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT LIKE', $condition, [], true); + } + + /** + * 指定Between查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereBetween($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'BETWEEN', $condition, [], true); + } + + /** + * 指定NotBetween查询条件 + * @access public + * @param mixed $field 查询字段 + * @param mixed $condition 查询条件 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereNotBetween($field, $condition, $logic = 'AND') + { + return $this->parseWhereExp($logic, $field, 'NOT BETWEEN', $condition, [], true); + } + + /** + * 比较两个字段 + * @access public + * @param string|array $field1 查询字段 + * @param string $operator 比较操作符 + * @param string $field2 比较字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereColumn($field1, $operator = null, $field2 = null, $logic = 'AND') + { + if (is_array($field1)) { + foreach ($field1 as $item) { + $this->whereColumn($item[0], $item[1], isset($item[2]) ? $item[2] : null); + } + return $this; + } + + if (is_null($field2)) { + $field2 = $operator; + $operator = '='; + } + + return $this->parseWhereExp($logic, $field1, 'COLUMN', [$operator, $field2], [], true); + } + + /** + * 设置软删除字段及条件 + * @access public + * @param false|string $field 查询字段 + * @param mixed $condition 查询条件 + * @return $this + */ + public function useSoftDelete($field, $condition = null) + { + if ($field) { + $this->options['soft_delete'] = [$field, $condition]; + } + + return $this; + } + + /** + * 指定Exp查询条件 + * @access public + * @param mixed $field 查询字段 + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereExp($field, $where, $bind = [], $logic = 'AND') + { + if ($bind) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = [$field, 'EXP', $this->raw($where)]; + + return $this; + } + + /** + * 指定表达式查询条件 + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function whereRaw($where, $bind = [], $logic = 'AND') + { + if ($bind) { + $this->bindParams($where, $bind); + } + + $this->options['where'][$logic][] = $this->raw($where); + + return $this; + } + + /** + * 参数绑定 + * @access public + * @param string $sql 绑定的sql表达式 + * @param array $bind 参数绑定 + * @return void + */ + protected function bindParams(&$sql, array $bind = []) + { + foreach ($bind as $key => $value) { + if (is_array($value)) { + $name = $this->bind($value[0], $value[1], isset($value[2]) ? $value[2] : null); + } else { + $name = $this->bind($value); + } + + if (is_numeric($key)) { + $sql = substr_replace($sql, ':' . $name, strpos($sql, '?'), 1); + } else { + $sql = str_replace(':' . $key, ':' . $name, $sql); + } + } + } + + /** + * 指定表达式查询条件 OR + * @access public + * @param string $where 查询条件 + * @param array $bind 参数绑定 + * @return $this + */ + public function whereOrRaw($where, $bind = []) + { + return $this->whereRaw($where, $bind, 'OR'); + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @param bool $strict 严格模式 + * @return $this + */ + protected function parseWhereExp($logic, $field, $op, $condition, array $param = [], $strict = false) + { + if ($field instanceof $this) { + $this->options['where'] = $field->getOptions('where'); + return $this; + } + + $logic = strtoupper($logic); + + if ($field instanceof Where) { + $this->options['where'][$logic] = $field->parse(); + return $this; + } + + if (is_string($field) && !empty($this->options['via']) && false === strpos($field, '.')) { + $field = $this->options['via'] . '.' . $field; + } + + if ($field instanceof Expression) { + return $this->whereRaw($field, is_array($op) ? $op : []); + } elseif ($strict) { + // 使用严格模式查询 + $where = [$field, $op, $condition]; + } elseif (is_array($field)) { + // 解析数组批量查询 + return $this->parseArrayWhereItems($field, $logic); + } elseif ($field instanceof \Closure) { + $where = $field; + } elseif (is_string($field)) { + if (preg_match('/[,=\<\'\"\(\s]/', $field)) { + return $this->whereRaw($field, $op); + } elseif (is_string($op) && strtolower($op) == 'exp') { + $bind = isset($param[2]) && is_array($param[2]) ? $param[2] : null; + return $this->whereExp($field, $condition, $bind, $logic); + } + + $where = $this->parseWhereItem($logic, $field, $op, $condition, $param); + } + + if (!empty($where)) { + $this->options['where'][$logic][] = $where; + } + + return $this; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $logic 查询逻辑 and or xor + * @param mixed $field 查询字段 + * @param mixed $op 查询表达式 + * @param mixed $condition 查询条件 + * @param array $param 查询参数 + * @return mixed + */ + protected function parseWhereItem($logic, $field, $op, $condition, $param = []) + { + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($param, $field); + $where = $param; + } elseif ($field && is_null($condition)) { + if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) { + $where = [$field, 'NULL', '']; + } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } elseif (in_array(strtoupper($op), ['REGEXP', 'NOT REGEXP', 'EXISTS', 'NOT EXISTS', 'NOTEXISTS'], true)) { + $where = [$field, $op, is_string($condition) ? $this->raw($condition) : $condition]; + } else { + $where = $field ? [$field, $op, $condition, isset($param[2]) ? $param[2] : null] : null; + } + + return $where; + } + + /** + * 数组批量查询 + * @access protected + * @param array $field 批量查询 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + protected function parseArrayWhereItems($field, $logic) + { + if (key($field) !== 0) { + $where = []; + foreach ($field as $key => $val) { + if ($val instanceof Expression) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } else { + $where[] = [$key, is_array($val) ? 'IN' : '=', $val]; + } + } + } else { + // 数组批量查询 + $where = $field; + } + + if (!empty($where)) { + $this->options['where'][$logic] = isset($this->options['where'][$logic]) ? array_merge($this->options['where'][$logic], $where) : $where; + } + + return $this; + } + + /** + * 去除某个查询条件 + * @access public + * @param string $field 查询字段 + * @param string $logic 查询逻辑 and or xor + * @return $this + */ + public function removeWhereField($field, $logic = 'AND') + { + $logic = strtoupper($logic); + + if (isset($this->options['where'][$logic])) { + foreach ($this->options['where'][$logic] as $key => $val) { + if (is_array($val) && $val[0] == $field) { + unset($this->options['where'][$logic][$key]); + } + } + } + + return $this; + } + + /** + * 去除查询参数 + * @access public + * @param string|bool $option 参数名 true 表示去除所有参数 + * @return $this + */ + public function removeOption($option = true) + { + if (true === $option) { + $this->options = []; + } elseif (is_string($option) && isset($this->options[$option])) { + unset($this->options[$option]); + } + + return $this; + } + + /** + * 条件查询 + * @access public + * @param mixed $condition 满足条件(支持闭包) + * @param \Closure|array $query 满足条件后执行的查询表达式(闭包或数组) + * @param \Closure|array $otherwise 不满足条件后执行 + * @return $this + */ + public function when($condition, $query, $otherwise = null) + { + if ($condition instanceof \Closure) { + $condition = $condition($this); + } + + if ($condition) { + if ($query instanceof \Closure) { + $query($this, $condition); + } elseif (is_array($query)) { + $this->where($query); + } + } elseif ($otherwise) { + if ($otherwise instanceof \Closure) { + $otherwise($this, $condition); + } elseif (is_array($otherwise)) { + $this->where($otherwise); + } + } + + return $this; + } + + /** + * 指定查询数量 + * @access public + * @param mixed $offset 起始位置 + * @param mixed $length 查询数量 + * @return $this + */ + public function limit($offset, $length = null) + { + if (is_null($length) && strpos($offset, ',')) { + list($offset, $length) = explode(',', $offset); + } + + $this->options['limit'] = intval($offset) . ($length ? ',' . intval($length) : ''); + + return $this; + } + + /** + * 指定分页 + * @access public + * @param mixed $page 页数 + * @param mixed $listRows 每页数量 + * @return $this + */ + public function page($page, $listRows = null) + { + if (is_null($listRows) && strpos($page, ',')) { + list($page, $listRows) = explode(',', $page); + } + + $this->options['page'] = [intval($page), intval($listRows)]; + + return $this; + } + + /** + * 分页查询 + * @access public + * @param int|array $listRows 每页数量 数组表示配置参数 + * @param int|bool $simple 是否简洁模式或者总记录数 + * @param array $config 配置参数 + * page:当前页, + * path:url路径, + * query:url额外参数, + * fragment:url锚点, + * var_page:分页变量, + * list_rows:每页数量 + * type:分页类名 + * @return \think\Paginator + * @throws DbException + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + if (is_int($simple)) { + $total = $simple; + $simple = false; + } + + $paginate = Container::get('config')->pull('paginate'); + + if (is_array($listRows)) { + $config = array_merge($paginate, $listRows); + $listRows = $config['list_rows']; + } else { + $config = array_merge($paginate, $config); + $listRows = $listRows ?: $config['list_rows']; + } + + /** @var Paginator $class */ + $class = false !== strpos($config['type'], '\\') ? $config['type'] : '\\think\\paginator\\driver\\' . ucwords($config['type']); + $page = isset($config['page']) ? (int) $config['page'] : call_user_func([ + $class, + 'getCurrentPage', + ], $config['var_page']); + + $page = $page < 1 ? 1 : $page; + + $config['path'] = isset($config['path']) ? $config['path'] : call_user_func([$class, 'getCurrentPath']); + + if (!isset($total) && !$simple) { + $options = $this->getOptions(); + + unset($this->options['order'], $this->options['limit'], $this->options['page'], $this->options['field']); + + $bind = $this->bind; + $total = $this->count(); + $results = $this->options($options)->bind($bind)->page($page, $listRows)->select(); + } elseif ($simple) { + $results = $this->limit(($page - 1) * $listRows, $listRows + 1)->select(); + $total = null; + } else { + $results = $this->page($page, $listRows)->select(); + } + + $this->removeOption('limit'); + $this->removeOption('page'); + + return $class::make($results, $listRows, $page, $total, $simple, $config); + } + + /** + * 指定当前操作的数据表 + * @access public + * @param mixed $table 表名 + * @return $this + */ + public function table($table) + { + if (is_string($table)) { + if (strpos($table, ')')) { + // 子查询 + } elseif (strpos($table, ',')) { + $tables = explode(',', $table); + $table = []; + + foreach ($tables as $item) { + list($item, $alias) = explode(' ', trim($item)); + if ($alias) { + $this->alias([$item => $alias]); + $table[$item] = $alias; + } else { + $table[] = $item; + } + } + } elseif (strpos($table, ' ')) { + list($table, $alias) = explode(' ', $table); + + $table = [$table => $alias]; + $this->alias($table); + } + } else { + $tables = $table; + $table = []; + + foreach ($tables as $key => $val) { + if (is_numeric($key)) { + $table[] = $val; + } else { + $this->alias([$key => $val]); + $table[$key] = $val; + } + } + } + + $this->options['table'] = $table; + + return $this; + } + + /** + * USING支持 用于多表删除 + * @access public + * @param mixed $using + * @return $this + */ + public function using($using) + { + $this->options['using'] = $using; + return $this; + } + + /** + * 指定排序 order('id','desc') 或者 order(['id'=>'desc','create_time'=>'desc']) + * @access public + * @param string|array $field 排序字段 + * @param string $order 排序 + * @return $this + */ + public function order($field, $order = null) + { + if (empty($field)) { + return $this; + } elseif ($field instanceof Expression) { + $this->options['order'][] = $field; + return $this; + } + + if (is_string($field)) { + if (!empty($this->options['via'])) { + $field = $this->options['via'] . '.' . $field; + } + + if (strpos($field, ',')) { + $field = array_map('trim', explode(',', $field)); + } else { + $field = empty($order) ? $field : [$field => $order]; + } + } elseif (!empty($this->options['via'])) { + foreach ($field as $key => $val) { + if (is_numeric($key)) { + $field[$key] = $this->options['via'] . '.' . $val; + } else { + $field[$this->options['via'] . '.' . $key] = $val; + unset($field[$key]); + } + } + } + + if (!isset($this->options['order'])) { + $this->options['order'] = []; + } + + if (is_array($field)) { + $this->options['order'] = array_merge($this->options['order'], $field); + } else { + $this->options['order'][] = $field; + } + + return $this; + } + + /** + * 表达式方式指定Field排序 + * @access public + * @param string $field 排序字段 + * @param array $bind 参数绑定 + * @return $this + */ + public function orderRaw($field, $bind = []) + { + if ($bind) { + $this->bindParams($field, $bind); + } + + $this->options['order'][] = $this->raw($field); + + return $this; + } + + /** + * 指定Field排序 order('id',[1,2,3],'desc') + * @access public + * @param string|array $field 排序字段 + * @param array $values 排序值 + * @param string $order + * @return $this + */ + public function orderField($field, array $values, $order = '') + { + if (!empty($values)) { + $values['sort'] = $order; + + $this->options['order'][$field] = $values; + } + + return $this; + } + + /** + * 随机排序 + * @access public + * @return $this + */ + public function orderRand() + { + $this->options['order'][] = '[rand]'; + return $this; + } + + /** + * 查询缓存 + * @access public + * @param mixed $key 缓存key + * @param integer|\DateTime $expire 缓存有效期 + * @param string $tag 缓存标签 + * @return $this + */ + public function cache($key = true, $expire = null, $tag = null) + { + // 增加快捷调用方式 cache(10) 等同于 cache(true, 10) + if ($key instanceof \DateTime || (is_numeric($key) && is_null($expire))) { + $expire = $key; + $key = true; + } + + if (false !== $key) { + $this->options['cache'] = ['key' => $key, 'expire' => $expire, 'tag' => $tag]; + } + + return $this; + } + + /** + * 指定group查询 + * @access public + * @param string|array $group GROUP + * @return $this + */ + public function group($group) + { + $this->options['group'] = $group; + return $this; + } + + /** + * 指定having查询 + * @access public + * @param string $having having + * @return $this + */ + public function having($having) + { + $this->options['having'] = $having; + return $this; + } + + /** + * 指定查询lock + * @access public + * @param bool|string $lock 是否lock + * @return $this + */ + public function lock($lock = false) + { + $this->options['lock'] = $lock; + $this->options['master'] = true; + + return $this; + } + + /** + * 指定distinct查询 + * @access public + * @param string $distinct 是否唯一 + * @return $this + */ + public function distinct($distinct) + { + $this->options['distinct'] = $distinct; + return $this; + } + + /** + * 指定数据表别名 + * @access public + * @param array|string $alias 数据表别名 + * @return $this + */ + public function alias($alias) + { + if (is_array($alias)) { + foreach ($alias as $key => $val) { + if (false !== strpos($key, '__')) { + $table = $this->connection->parseSqlTable($key); + } else { + $table = $key; + } + $this->options['alias'][$table] = $val; + } + } else { + if (isset($this->options['table'])) { + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + if (false !== strpos($table, '__')) { + $table = $this->connection->parseSqlTable($table); + } + } else { + $table = $this->getTable(); + } + + $this->options['alias'][$table] = $alias; + } + + return $this; + } + + /** + * 指定强制索引 + * @access public + * @param string $force 索引名称 + * @return $this + */ + public function force($force) + { + $this->options['force'] = $force; + return $this; + } + + /** + * 查询注释 + * @access public + * @param string $comment 注释 + * @return $this + */ + public function comment($comment) + { + $this->options['comment'] = $comment; + return $this; + } + + /** + * 获取执行的SQL语句 + * @access public + * @param boolean $fetch 是否返回sql + * @return $this + */ + public function fetchSql($fetch = true) + { + $this->options['fetch_sql'] = $fetch; + return $this; + } + + /** + * 不主动获取数据集 + * @access public + * @param bool $pdo 是否返回 PDOStatement 对象 + * @return $this + */ + public function fetchPdo($pdo = true) + { + $this->options['fetch_pdo'] = $pdo; + return $this; + } + + /** + * 设置是否返回数据集对象(支持设置数据集对象类名) + * @access public + * @param bool|string $collection 是否返回数据集对象 + * @return $this + */ + public function fetchCollection($collection = true) + { + $this->options['collection'] = $collection; + + return $this; + } + + /** + * 设置从主服务器读取数据 + * @access public + * @return $this + */ + public function master() + { + $this->options['master'] = true; + return $this; + } + + /** + * 设置是否严格检查字段名 + * @access public + * @param bool $strict 是否严格检查字段 + * @return $this + */ + public function strict($strict = true) + { + $this->options['strict'] = $strict; + return $this; + } + + /** + * 设置查询数据不存在是否抛出异常 + * @access public + * @param bool $fail 数据不存在是否抛出异常 + * @return $this + */ + public function failException($fail = true) + { + $this->options['fail'] = $fail; + return $this; + } + + /** + * 设置自增序列名 + * @access public + * @param string $sequence 自增序列名 + * @return $this + */ + public function sequence($sequence = null) + { + $this->options['sequence'] = $sequence; + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param mixed $hidden 需要隐藏的字段名 + * @return $this + */ + public function hidden($hidden) + { + if ($this->model) { + $this->options['hidden'] = $hidden; + return $this; + } + + return $this->field($hidden, true); + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible 需要输出的属性 + * @return $this + */ + public function visible(array $visible) + { + $this->options['visible'] = $visible; + return $this; + } + + /** + * 设置需要追加输出的属性 + * @access public + * @param array $append 需要追加的属性 + * @return $this + */ + public function append(array $append) + { + $this->options['append'] = $append; + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + if (is_array($name)) { + $this->options['with_attr'] = $name; + } else { + $this->options['with_attr'][$name] = $callback; + } + + return $this; + } + + /** + * 设置JSON字段信息 + * @access public + * @param array $json JSON字段 + * @param bool $assoc 是否取出数组 + * @return $this + */ + public function json(array $json = [], $assoc = false) + { + $this->options['json'] = $json; + $this->options['json_assoc'] = $assoc; + return $this; + } + + /** + * 设置字段类型信息 + * @access public + * @param array $type 字段类型信息 + * @return $this + */ + public function setJsonFieldType(array $type) + { + $this->options['field_type'] = $type; + return $this; + } + + /** + * 获取字段类型信息 + * @access public + * @param string $field 字段名 + * @return string|null + */ + public function getJsonFieldType($field) + { + return isset($this->options['field_type'][$field]) ? $this->options['field_type'][$field] : null; + } + + /** + * 是否允许返回空数据(或空模型) + * @access public + * @param bool $allowEmpty 是否允许为空 + * @return $this + */ + public function allowEmpty($allowEmpty = true) + { + $this->options['allow_empty'] = $allowEmpty; + return $this; + } + + /** + * 添加查询范围 + * @access public + * @param array|string|\Closure $scope 查询范围定义 + * @param array $args 参数 + * @return $this + */ + public function scope($scope, ...$args) + { + // 查询范围的第一个参数始终是当前查询对象 + array_unshift($args, $this); + + if ($scope instanceof \Closure) { + call_user_func_array($scope, $args); + return $this; + } + + if (is_string($scope)) { + $scope = explode(',', $scope); + } + + if ($this->model) { + // 检查模型类的查询范围方法 + foreach ($scope as $name) { + $method = 'scope' . trim($name); + + if (method_exists($this->model, $method)) { + call_user_func_array([$this->model, $method], $args); + } + } + } + + return $this; + } + + /** + * 使用搜索器条件搜索字段 + * @access public + * @param array $fields 搜索字段 + * @param array $data 搜索数据 + * @param string $prefix 字段前缀标识 + * @return $this + */ + public function withSearch(array $fields, array $data = [], $prefix = '') + { + foreach ($fields as $key => $field) { + if ($field instanceof \Closure) { + $field($this, isset($data[$key]) ? $data[$key] : null, $data, $prefix); + } elseif ($this->model) { + // 检测搜索器 + $fieldName = is_numeric($key) ? $field : $key; + $method = 'search' . Loader::parseName($fieldName, 1) . 'Attr'; + + if (method_exists($this->model, $method)) { + $this->model->$method($this, isset($data[$field]) ? $data[$field] : null, $data, $prefix); + } + } + } + + return $this; + } + + /** + * 指定数据表主键 + * @access public + * @param string $pk 主键 + * @return $this + */ + public function pk($pk) + { + $this->pk = $pk; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $name 时间表达式 + * @param string|array $rule 时间范围 + * @return $this + */ + public function timeRule($name, $rule) + { + $this->timeRule[$name] = $rule; + return $this; + } + + /** + * 查询日期或者时间 + * @access public + * @param string $field 日期字段名 + * @param string|array $op 比较运算符或者表达式 + * @param string|array $range 比较范围 + * @param string $logic AND OR + * @return $this + */ + public function whereTime($field, $op, $range = null, $logic = 'AND') + { + if (is_null($range)) { + if (is_array($op)) { + $range = $op; + } else { + if (isset($this->timeExp[strtolower($op)])) { + $op = $this->timeExp[strtolower($op)]; + } + + if (isset($this->timeRule[strtolower($op)])) { + $range = $this->timeRule[strtolower($op)]; + } else { + $range = $op; + } + } + + $op = is_array($range) ? 'between' : '>='; + } + + return $this->parseWhereExp($logic, $field, strtolower($op) . ' time', $range, [], true); + } + + /** + * 查询当前时间在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '<=', time()) + ->whereTime($endField, '>=', time()); + } + + /** + * 查询当前时间不在两个时间字段范围 + * @access public + * @param string $startField 开始时间字段 + * @param string $endField 结束时间字段 + * @return $this + */ + public function whereNotBetweenTimeField($startField, $endField) + { + return $this->whereTime($startField, '>', time()) + ->whereTime($endField, '<', time(), 'OR'); + } + + /** + * 查询日期或者时间范围 + * @access public + * @param string $field 日期字段名 + * @param string $startTime 开始时间 + * @param string $endTime 结束时间 + * @param string $logic AND OR + * @return $this + */ + public function whereBetweenTime($field, $startTime, $endTime = null, $logic = 'AND') + { + if (is_null($endTime)) { + $time = is_string($startTime) ? strtotime($startTime) : $startTime; + $endTime = strtotime('+1 day', $time); + } + + return $this->parseWhereExp($logic, $field, 'between time', [$startTime, $endTime], [], true); + } + + /** + * 获取当前数据表的主键 + * @access public + * @param string|array $options 数据表名或者查询参数 + * @return string|array + */ + public function getPk($options = '') + { + if (!empty($this->pk)) { + $pk = $this->pk; + } else { + $pk = $this->connection->getPk(is_array($options) && isset($options['table']) ? $options['table'] : $this->getTable()); + } + + return $pk; + } + + /** + * 参数绑定 + * @access public + * @param mixed $value 绑定变量值 + * @param integer $type 绑定类型 + * @param string $name 绑定名称 + * @return $this|string + */ + public function bind($value, $type = PDO::PARAM_STR, $name = null) + { + if (is_array($value)) { + $this->bind = array_merge($this->bind, $value); + } else { + $name = $name ?: 'ThinkBind_' . (count($this->bind) + 1) . '_'; + + $this->bind[$name] = [$value, $type]; + return $name; + } + + return $this; + } + + /** + * 检测参数是否已经绑定 + * @access public + * @param string $key 参数名 + * @return bool + */ + public function isBind($key) + { + return isset($this->bind[$key]); + } + + /** + * 查询参数赋值 + * @access public + * @param string $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value) + { + $this->options[$name] = $value; + return $this; + } + + /** + * 查询参数赋值 + * @access protected + * @param array $options 表达式参数 + * @return $this + */ + protected function options(array $options) + { + $this->options = $options; + return $this; + } + + /** + * 获取当前的查询参数 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOptions($name = '') + { + if ('' === $name) { + return $this->options; + } + return isset($this->options[$name]) ? $this->options[$name] : null; + } + + /** + * 设置当前的查询参数 + * @access public + * @param string $option 参数名 + * @param mixed $value 参数值 + * @return $this + */ + public function setOption($option, $value) + { + $this->options[$option] = $value; + return $this; + } + + /** + * 设置关联查询JOIN预查询 + * @access public + * @param string|array $with 关联方法名称 + * @return $this + */ + public function with($with) + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $closure = null; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + + if ($model instanceof OneToOne && 0 == $model->getEagerlyType()) { + $table = $model->getTable(); + $model->removeOption() + ->table($table) + ->eagerly($this, $relation, true, '', $closure, $first); + $first = false; + } + } + + $this->via(); + + $this->options['with'] = $with; + + return $this; + } + + /** + * 关联预载入 JOIN方式(不支持嵌套) + * @access protected + * @param string|array $with 关联方法名 + * @param string $joinType JOIN方式 + * @return $this + */ + public function withJoin($with, $joinType = '') + { + if (empty($with)) { + return $this; + } + + if (is_string($with)) { + $with = explode(',', $with); + } + + $first = true; + + /** @var Model $class */ + $class = $this->model; + foreach ($with as $key => $relation) { + $closure = null; + $field = true; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } elseif (is_array($relation)) { + $field = $relation; + $relation = $key; + } elseif (is_string($relation) && strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + /** @var Relation $model */ + $relation = Loader::parseName($relation, 1, false); + $model = $class->$relation(); + + if ($model instanceof OneToOne) { + $model->eagerly($this, $relation, $field, $joinType, $closure, $first); + $first = false; + } else { + // 不支持其它关联 + unset($with[$key]); + } + } + + $this->via(); + + $this->options['with_join'] = $with; + + return $this; + } + + /** + * 关联统计 + * @access protected + * @param string|array $relation 关联方法名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + protected function withAggregate($relation, $aggregate = 'count', $field = '*', $subQuery = true) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + if (!$subQuery) { + $this->options['with_count'][] = [$relations, $aggregate, $field]; + } else { + if (!isset($this->options['field'])) { + $this->field('*'); + } + + foreach ($relations as $key => $relation) { + $closure = $aggregateField = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (!is_int($key)) { + $aggregateField = $relation; + $relation = $key; + } + + $relation = Loader::parseName($relation, 1, false); + + $count = $this->model->$relation()->getRelationCountQuery($closure, $aggregate, $field, $aggregateField); + + if (empty($aggregateField)) { + $aggregateField = Loader::parseName($relation) . '_' . $aggregate; + } + + $this->field(['(' . $count . ')' => $aggregateField]); + } + } + + return $this; + } + + /** + * 关联统计 + * @access public + * @param string|array $relation 关联方法名 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withCount($relation, $subQuery = true) + { + return $this->withAggregate($relation, 'count', '*', $subQuery); + } + + /** + * 关联统计Sum + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withSum($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'sum', $field, $subQuery); + } + + /** + * 关联统计Max + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMax($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'max', $field, $subQuery); + } + + /** + * 关联统计Min + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withMin($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'min', $field, $subQuery); + } + + /** + * 关联统计Avg + * @access public + * @param string|array $relation 关联方法名 + * @param string $field 字段 + * @param bool $subQuery 是否使用子查询 + * @return $this + */ + public function withAvg($relation, $field, $subQuery = true) + { + return $this->withAggregate($relation, 'avg', $field, $subQuery); + } + + /** + * 关联预加载中 获取关联指定字段值 + * example: + * Model::with(['relation' => function($query){ + * $query->withField("id,name"); + * }]) + * + * @access public + * @param string | array $field 指定获取的字段 + * @return $this + */ + public function withField($field) + { + $this->options['with_field'] = $field; + + return $this; + } + + /** + * 设置当前字段添加的表别名 + * @access public + * @param string $via + * @return $this + */ + public function via($via = '') + { + $this->options['via'] = $via; + + return $this; + } + + /** + * 设置关联查询 + * @access public + * @param string|array $relation 关联名称 + * @return $this + */ + public function relation($relation) + { + if (empty($relation)) { + return $this; + } + + if (is_string($relation)) { + $relation = explode(',', $relation); + } + + if (isset($this->options['relation'])) { + $this->options['relation'] = array_merge($this->options['relation'], $relation); + } else { + $this->options['relation'] = $relation; + } + + return $this; + } + + /** + * 插入记录 + * @access public + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param boolean $getLastInsID 返回自增主键 + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insert(array $data = [], $replace = false, $getLastInsID = false, $sequence = null) + { + $this->parseOptions(); + + $this->options['data'] = array_merge($this->options['data'], $data); + + return $this->connection->insert($this, $replace, $getLastInsID, $sequence); + } + + /** + * 插入记录并获取自增ID + * @access public + * @param array $data 数据 + * @param boolean $replace 是否replace + * @param string $sequence 自增序列名 + * @return integer|string + */ + public function insertGetId(array $data, $replace = false, $sequence = null) + { + return $this->insert($data, $replace, true, $sequence); + } + + /** + * 批量插入记录 + * @access public + * @param array $dataSet 数据集 + * @param boolean $replace 是否replace + * @param integer $limit 每次写入数据限制 + * @return integer|string + */ + public function insertAll(array $dataSet = [], $replace = false, $limit = null) + { + $this->parseOptions(); + + if (empty($dataSet)) { + $dataSet = $this->options['data']; + } + + if (empty($limit) && !empty($this->options['limit'])) { + $limit = $this->options['limit']; + } + + return $this->connection->insertAll($this, $dataSet, $replace, $limit); + } + + /** + * 通过Select方式插入记录 + * @access public + * @param string $fields 要插入的数据表字段名 + * @param string $table 要插入的数据表名 + * @return integer|string + * @throws PDOException + */ + public function selectInsert($fields, $table) + { + $this->parseOptions(); + + return $this->connection->selectInsert($this, $fields, $table); + } + + /** + * 更新记录 + * @access public + * @param mixed $data 数据 + * @return integer|string + * @throws Exception + * @throws PDOException + */ + public function update(array $data = []) + { + $this->parseOptions(); + + $this->options['data'] = array_merge($this->options['data'], $data); + + return $this->connection->update($this); + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + $this->parseOptions(); + + if (!is_null($data) && true !== $data) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + if (!empty($this->options['soft_delete'])) { + // 软删除 + list($field, $condition) = $this->options['soft_delete']; + if ($condition) { + unset($this->options['soft_delete']); + $this->options['data'] = [$field => $condition]; + + return $this->connection->update($this); + } + } + + $this->options['data'] = $data; + + return $this->connection->delete($this); + } + + /** + * 执行查询但只返回PDOStatement对象 + * @access public + * @return \PDOStatement|string + */ + public function getPdo() + { + $this->parseOptions(); + + return $this->connection->pdo($this); + } + + /** + * 使用游标查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return \Generator + */ + public function cursor($data = null) + { + if ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $connection = clone $this->connection; + + return $connection->cursor($this); + } + + /** + * 查找记录 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection|array|\PDOStatement|string + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function select($data = null) + { + if ($data instanceof Query) { + return $data->select(); + } elseif ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (false === $data) { + // 用于子查询 不查询只返回SQL + $this->options['fetch_sql'] = true; + } elseif (!is_null($data)) { + // 主键条件分析 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $resultSet = $this->connection->select($this); + + if ($this->options['fetch_sql']) { + return $resultSet; + } + + // 返回结果处理 + if (!empty($this->options['fail']) && count($resultSet) == 0) { + $this->throwNotFound($this->options); + } + + // 数据列表读取后的处理 + if (!empty($this->model)) { + // 生成模型对象 + $resultSet = $this->resultSetToModelCollection($resultSet); + } else { + $this->resultSet($resultSet); + } + + return $resultSet; + } + + /** + * 查询数据转换为模型数据集对象 + * @access protected + * @param array $resultSet 数据集 + * @return ModelCollection + */ + protected function resultSetToModelCollection(array $resultSet) + { + if (!empty($this->options['collection']) && is_string($this->options['collection'])) { + $collection = $this->options['collection']; + } + + if (empty($resultSet)) { + return $this->model->toCollection([], isset($collection) ? $collection : null); + } + + // 检查动态获取器 + if (!empty($this->options['with_attr'])) { + foreach ($this->options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + list($relation, $field) = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($this->options['with_attr'][$name]); + } + } + } + + $withRelationAttr = isset($withRelationAttr) ? $withRelationAttr : []; + + foreach ($resultSet as $key => &$result) { + // 数据转换为模型对象 + $this->resultToModel($result, $this->options, true, $withRelationAttr); + } + + if (!empty($this->options['with'])) { + // 预载入 + $result->eagerlyResultSet($resultSet, $this->options['with'], $withRelationAttr); + } + + if (!empty($this->options['with_join'])) { + // JOIN预载入 + $result->eagerlyResultSet($resultSet, $this->options['with_join'], $withRelationAttr, true); + } + + // 模型数据集转换 + return $result->toCollection($resultSet, isset($collection) ? $collection : null); + } + + /** + * 处理数据集 + * @access public + * @param array $resultSet + * @return void + */ + protected function resultSet(&$resultSet) + { + if (!empty($this->options['json'])) { + foreach ($resultSet as &$result) { + $this->jsonResult($result, $this->options['json'], true); + } + } + + if (!empty($this->options['with_attr'])) { + foreach ($resultSet as &$result) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + if (!empty($this->options['collection']) || 'collection' == $this->connection->getConfig('resultset_type')) { + // 返回Collection对象 + $resultSet = new Collection($resultSet); + } + } + + /** + * 查找单条记录 + * @access public + * @param array|string|Query|\Closure $data + * @return array|null|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function find($data = null) + { + if ($data instanceof Query) { + return $data->find(); + } elseif ($data instanceof \Closure) { + $data($this); + $data = null; + } + + $this->parseOptions(); + + if (!is_null($data)) { + // AR模式分析主键条件 + $this->parsePkWhere($data); + } + + $this->options['data'] = $data; + + $result = $this->connection->find($this); + + if ($this->options['fetch_sql']) { + return $result; + } + + // 数据处理 + if (empty($result)) { + return $this->resultToEmpty(); + } + + if (!empty($this->model)) { + // 返回模型对象 + $this->resultToModel($result, $this->options); + } else { + $this->result($result); + } + + return $result; + } + + /** + * 处理空数据 + * @access protected + * @return array|Model|null + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function resultToEmpty() + { + if (!empty($this->options['allow_empty'])) { + return !empty($this->model) ? $this->model->newInstance([], $this->getModelUpdateCondition($this->options)) : []; + } elseif (!empty($this->options['fail'])) { + $this->throwNotFound($this->options); + } + } + + /** + * 查找单条记录 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @param bool $failException 是否抛出异常 + * @return static|null + * @throws exception\DbException + */ + public function get($data, $with = [], $cache = false, $failException = false) + { + if (is_null($data)) { + return; + } + + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + + return $this->parseQuery($data, $with, $cache) + ->failException($failException) + ->find($data); + } + + /** + * 查找单条记录 如果不存在直接抛出异常 + * @access public + * @param mixed $data 主键值或者查询条件(闭包) + * @param mixed $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static|null + * @throws exception\DbException + */ + public function getOrFail($data, $with = [], $cache = false) + { + return $this->get($data, $with, $cache, true); + } + + /** + * 查找所有记录 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param array|string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return static[]|false + * @throws exception\DbException + */ + public function all($data = null, $with = [], $cache = false) + { + if (true === $with || is_int($with)) { + $cache = $with; + $with = []; + } + + return $this->parseQuery($data, $with, $cache)->select($data); + } + + /** + * 分析查询表达式 + * @access public + * @param mixed $data 主键列表或者查询条件(闭包) + * @param string $with 关联预查询 + * @param bool $cache 是否缓存 + * @return Query + */ + protected function parseQuery(&$data, $with, $cache) + { + $result = $this->with($with)->cache($cache); + + if ((is_array($data) && key($data) !== 0) || $data instanceof Where) { + $result = $result->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + $data($result); + $data = null; + } elseif ($data instanceof Query) { + $result = $data->with($with)->cache($cache); + $data = null; + } + + return $result; + } + + /** + * 处理数据 + * @access protected + * @param array $result 查询数据 + * @return void + */ + protected function result(&$result) + { + if (!empty($this->options['json'])) { + $this->jsonResult($result, $this->options['json'], true); + } + + if (!empty($this->options['with_attr'])) { + $this->getResultAttr($result, $this->options['with_attr']); + } + } + + /** + * 使用获取器处理数据 + * @access protected + * @param array $result 查询数据 + * @param array $withAttr 字段获取器 + * @return void + */ + protected function getResultAttr(&$result, $withAttr = []) + { + foreach ($withAttr as $name => $closure) { + $name = Loader::parseName($name); + + if (strpos($name, '.')) { + // 支持JSON字段 获取器定义 + list($key, $field) = explode('.', $name); + + if (isset($result[$key])) { + $result[$key][$field] = $closure(isset($result[$key][$field]) ? $result[$key][$field] : null, $result[$key]); + } + } else { + $result[$name] = $closure(isset($result[$name]) ? $result[$name] : null, $result); + } + } + } + + /** + * JSON字段数据转换 + * @access protected + * @param array $result 查询数据 + * @param array $json JSON字段 + * @param bool $assoc 是否转换为数组 + * @param array $withRelationAttr 关联获取器 + * @return void + */ + protected function jsonResult(&$result, $json = [], $assoc = false, $withRelationAttr = []) + { + foreach ($json as $name) { + if (isset($result[$name])) { + $result[$name] = json_decode($result[$name], $assoc); + + if (isset($withRelationAttr[$name])) { + foreach ($withRelationAttr[$name] as $key => $closure) { + $data = get_object_vars($result[$name]); + $result[$name]->$key = $closure(isset($result[$name]->$key) ? $result[$name]->$key : null, $data); + } + } + } + } + } + + /** + * 查询数据转换为模型对象 + * @access protected + * @param array $result 查询数据 + * @param array $options 查询参数 + * @param bool $resultSet 是否为数据集查询 + * @param array $withRelationAttr 关联字段获取器 + * @return void + */ + protected function resultToModel(&$result, $options = [], $resultSet = false, $withRelationAttr = []) + { + // 动态获取器 + if (!empty($options['with_attr']) && empty($withRelationAttr)) { + foreach ($options['with_attr'] as $name => $val) { + if (strpos($name, '.')) { + list($relation, $field) = explode('.', $name); + + $withRelationAttr[$relation][$field] = $val; + unset($options['with_attr'][$name]); + } + } + } + + // JSON 数据处理 + if (!empty($options['json'])) { + $this->jsonResult($result, $options['json'], $options['json_assoc'], $withRelationAttr); + } + + $result = $this->model->newInstance($result, $resultSet ? null : $this->getModelUpdateCondition($options)); + + // 动态获取器 + if (!empty($options['with_attr'])) { + $result->withAttribute($options['with_attr']); + } + + // 输出属性控制 + if (!empty($options['visible'])) { + $result->visible($options['visible']); + } elseif (!empty($options['hidden'])) { + $result->hidden($options['hidden']); + } + + if (!empty($options['append'])) { + $result->append($options['append']); + } + + // 关联查询 + if (!empty($options['relation'])) { + $result->relationQuery($options['relation'], $withRelationAttr); + } + + // 预载入查询 + if (!$resultSet && !empty($options['with'])) { + $result->eagerlyResult($result, $options['with'], $withRelationAttr); + } + + // JOIN预载入查询 + if (!$resultSet && !empty($options['with_join'])) { + $result->eagerlyResult($result, $options['with_join'], $withRelationAttr, true); + } + + // 关联统计 + if (!empty($options['with_count'])) { + foreach ($options['with_count'] as $val) { + $result->relationCount($result, $val[0], $val[1], $val[2]); + } + } + } + + /** + * 获取模型的更新条件 + * @access protected + * @param array $options 查询参数 + */ + protected function getModelUpdateCondition(array $options) + { + return isset($options['where']['AND']) ? $options['where']['AND'] : null; + } + + /** + * 查询失败 抛出异常 + * @access protected + * @param array $options 查询参数 + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + protected function throwNotFound($options = []) + { + if (!empty($this->model)) { + $class = get_class($this->model); + throw new ModelNotFoundException('model data Not Found:' . $class, $class, $options); + } + $table = is_array($options['table']) ? key($options['table']) : $options['table']; + throw new DataNotFoundException('table data not Found:' . $table, $table, $options); + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return array|\PDOStatement|string|Model + * @throws DbException + * @throws ModelNotFoundException + * @throws DataNotFoundException + */ + public function findOrEmpty($data = null) + { + return $this->allowEmpty(true)->find($data); + } + + /** + * 分批数据返回处理 + * @access public + * @param integer $count 每次处理的数据数量 + * @param callable $callback 处理回调方法 + * @param string|array $column 分批处理的字段名 + * @param string $order 字段排序 + * @return boolean + * @throws DbException + */ + public function chunk($count, $callback, $column = null, $order = 'asc') + { + $options = $this->getOptions(); + $column = $column ?: $this->getPk($options); + + if (isset($options['order'])) { + if (Container::get('app')->isDebug()) { + throw new DbException('chunk not support call order'); + } + unset($options['order']); + } + + $bind = $this->bind; + + if (is_array($column)) { + $times = 1; + $query = $this->options($options)->page($times, $count); + } else { + $query = $this->options($options)->limit($count); + + if (strpos($column, '.')) { + list($alias, $key) = explode('.', $column); + } else { + $key = $column; + } + } + + $resultSet = $query->order($column, $order)->select(); + + while (count($resultSet) > 0) { + if ($resultSet instanceof Collection) { + $resultSet = $resultSet->all(); + } + + if (false === call_user_func($callback, $resultSet)) { + return false; + } + + if (isset($times)) { + $times++; + $query = $this->options($options)->page($times, $count); + } else { + $end = end($resultSet); + $lastId = is_array($end) ? $end[$key] : $end->getData($key); + + $query = $this->options($options) + ->limit($count) + ->where($column, 'asc' == strtolower($order) ? '>' : '<', $lastId); + } + + $resultSet = $query->bind($bind)->order($column, $order)->select(); + } + + return true; + } + + /** + * 获取绑定的参数 并清空 + * @access public + * @param bool $clear + * @return array + */ + public function getBind($clear = true) + { + $bind = $this->bind; + if ($clear) { + $this->bind = []; + } + + return $bind; + } + + /** + * 创建子查询SQL + * @access public + * @param bool $sub + * @return string + * @throws DbException + */ + public function buildSql($sub = true) + { + return $sub ? '( ' . $this->select(false) . ' )' : $this->select(false); + } + + /** + * 视图查询处理 + * @access protected + * @param array $options 查询参数 + * @return void + */ + protected function parseView(&$options) + { + if (!isset($options['map'])) { + return; + } + + foreach (['AND', 'OR'] as $logic) { + if (isset($options['where'][$logic])) { + foreach ($options['where'][$logic] as $key => $val) { + if (array_key_exists($key, $options['map'])) { + array_shift($val); + array_unshift($val, $options['map'][$key]); + $options['where'][$logic][$options['map'][$key]] = $val; + unset($options['where'][$logic][$key]); + } + } + } + } + + if (isset($options['order'])) { + // 视图查询排序处理 + if (is_string($options['order'])) { + $options['order'] = explode(',', $options['order']); + } + foreach ($options['order'] as $key => $val) { + if (is_numeric($key) && is_string($val)) { + if (strpos($val, ' ')) { + list($field, $sort) = explode(' ', $val); + if (array_key_exists($field, $options['map'])) { + $options['order'][$options['map'][$field]] = $sort; + unset($options['order'][$key]); + } + } elseif (array_key_exists($val, $options['map'])) { + $options['order'][$options['map'][$val]] = 'asc'; + unset($options['order'][$key]); + } + } elseif (array_key_exists($key, $options['map'])) { + $options['order'][$options['map'][$key]] = $val; + unset($options['order'][$key]); + } + } + } + } + + /** + * 把主键值转换为查询条件 支持复合主键 + * @access public + * @param array|string $data 主键数据 + * @return void + * @throws Exception + */ + public function parsePkWhere($data) + { + $pk = $this->getPk($this->options); + // 获取当前数据表 + $table = is_array($this->options['table']) ? key($this->options['table']) : $this->options['table']; + + if (!empty($this->options['alias'][$table])) { + $alias = $this->options['alias'][$table]; + } + + if (is_string($pk)) { + $key = isset($alias) ? $alias . '.' . $pk : $pk; + // 根据主键查询 + if (is_array($data)) { + $where[$pk] = isset($data[$pk]) ? [$key, '=', $data[$pk]] : [$key, 'in', $data]; + } else { + $where[$pk] = strpos($data, ',') ? [$key, 'IN', $data] : [$key, '=', $data]; + } + } elseif (is_array($pk) && is_array($data) && !empty($data)) { + // 根据复合主键查询 + foreach ($pk as $key) { + if (isset($data[$key])) { + $attr = isset($alias) ? $alias . '.' . $key : $key; + $where[$key] = [$attr, '=', $data[$key]]; + } else { + throw new Exception('miss complex primary data'); + } + } + } + + if (!empty($where)) { + if (isset($this->options['where']['AND'])) { + $this->options['where']['AND'] = array_merge($this->options['where']['AND'], $where); + } else { + $this->options['where']['AND'] = $where; + } + } + + return; + } + + /** + * 分析表达式(可用于查询或者写入操作) + * @access protected + * @return array + */ + protected function parseOptions() + { + $options = $this->getOptions(); + + // 获取数据表 + if (empty($options['table'])) { + $options['table'] = $this->getTable(); + } + + if (!isset($options['where'])) { + $options['where'] = []; + } elseif (isset($options['view'])) { + // 视图查询条件处理 + $this->parseView($options); + } + + if (!isset($options['field'])) { + $options['field'] = '*'; + } + + foreach (['data', 'order', 'join', 'union'] as $name) { + if (!isset($options[$name])) { + $options[$name] = []; + } + } + + if (!isset($options['strict'])) { + $options['strict'] = $this->getConfig('fields_strict'); + } + + foreach (['master', 'lock', 'fetch_pdo', 'fetch_sql', 'distinct'] as $name) { + if (!isset($options[$name])) { + $options[$name] = false; + } + } + + if (isset(static::$readMaster['*']) || (is_string($options['table']) && isset(static::$readMaster[$options['table']]))) { + $options['master'] = true; + } + + foreach (['group', 'having', 'limit', 'force', 'comment'] as $name) { + if (!isset($options[$name])) { + $options[$name] = ''; + } + } + + if (isset($options['page'])) { + // 根据页数计算limit + list($page, $listRows) = $options['page']; + $page = $page > 0 ? $page : 1; + $listRows = $listRows > 0 ? $listRows : (is_numeric($options['limit']) ? $options['limit'] : 20); + $offset = $listRows * ($page - 1); + $options['limit'] = $offset . ',' . $listRows; + } + + $this->options = $options; + + return $options; + } + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @return void + */ + public static function event($event, $callback) + { + self::$event[$event] = $callback; + } + + /** + * 触发事件 + * @access public + * @param string $event 事件名 + * @return bool + */ + public function trigger($event) + { + $result = false; + + if (isset(self::$event[$event])) { + $result = Container::getInstance()->invoke(self::$event[$event], [$this]); + } + + return $result; + } + +} diff --git a/thinkphp/library/think/db/Where.php b/thinkphp/library/think/db/Where.php new file mode 100644 index 0000000..9132e54 --- /dev/null +++ b/thinkphp/library/think/db/Where.php @@ -0,0 +1,178 @@ + +// +---------------------------------------------------------------------- + +namespace think\db; + +use ArrayAccess; + +class Where implements ArrayAccess +{ + /** + * 查询表达式 + * @var array + */ + protected $where = []; + + /** + * 是否需要增加括号 + * @var bool + */ + protected $enclose = false; + + /** + * 创建一个查询表达式 + * + * @param array $where 查询条件数组 + * @param bool $enclose 是否增加括号 + */ + public function __construct(array $where = [], $enclose = false) + { + $this->where = $where; + $this->enclose = $enclose; + } + + /** + * 设置是否添加括号 + * @access public + * @param bool $enclose + * @return $this + */ + public function enclose($enclose = true) + { + $this->enclose = $enclose; + return $this; + } + + /** + * 解析为Query对象可识别的查询条件数组 + * @access public + * @return array + */ + public function parse() + { + $where = []; + + foreach ($this->where as $key => $val) { + if ($val instanceof Expression) { + $where[] = [$key, 'exp', $val]; + } elseif (is_null($val)) { + $where[] = [$key, 'NULL', '']; + } elseif (is_array($val)) { + $where[] = $this->parseItem($key, $val); + } else { + $where[] = [$key, '=', $val]; + } + } + + return $this->enclose ? [$where] : $where; + } + + /** + * 分析查询表达式 + * @access protected + * @param string $field 查询字段 + * @param array $where 查询条件 + * @return array + */ + protected function parseItem($field, $where = []) + { + $op = $where[0]; + $condition = isset($where[1]) ? $where[1] : null; + + if (is_array($op)) { + // 同一字段多条件查询 + array_unshift($where, $field); + } elseif (is_null($condition)) { + if (in_array(strtoupper($op), ['NULL', 'NOTNULL', 'NOT NULL'], true)) { + // null查询 + $where = [$field, $op, '']; + } elseif (in_array($op, ['=', 'eq', 'EQ', null], true)) { + $where = [$field, 'NULL', '']; + } elseif (in_array($op, ['<>', 'neq', 'NEQ'], true)) { + $where = [$field, 'NOTNULL', '']; + } else { + // 字段相等查询 + $where = [$field, '=', $op]; + } + } else { + $where = [$field, $op, $condition]; + } + + return $where; + } + + /** + * 修改器 设置数据对象的值 + * @access public + * @param string $name 名称 + * @param mixed $value 值 + * @return void + */ + public function __set($name, $value) + { + $this->where[$name] = $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @return mixed + */ + public function __get($name) + { + return isset($this->where[$name]) ? $this->where[$name] : null; + } + + /** + * 检测数据对象的值 + * @access public + * @param string $name 名称 + * @return boolean + */ + public function __isset($name) + { + return isset($this->where[$name]); + } + + /** + * 销毁数据对象的值 + * @access public + * @param string $name 名称 + * @return void + */ + public function __unset($name) + { + unset($this->where[$name]); + } + + // ArrayAccess + public function offsetSet($name, $value) + { + $this->__set($name, $value); + } + + public function offsetExists($name) + { + return $this->__isset($name); + } + + public function offsetUnset($name) + { + $this->__unset($name); + } + + public function offsetGet($name) + { + return $this->__get($name); + } + +} diff --git a/thinkphp/library/think/db/builder/Mysql.php b/thinkphp/library/think/db/builder/Mysql.php new file mode 100644 index 0000000..7ec3bf8 --- /dev/null +++ b/thinkphp/library/think/db/builder/Mysql.php @@ -0,0 +1,174 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; +use think\db\Query; +use think\Exception; + +/** + * mysql数据库驱动 + */ +class Mysql extends Builder +{ + // 查询表达式解析 + protected $parser = [ + 'parseCompare' => ['=', '<>', '>', '>=', '<', '<='], + 'parseLike' => ['LIKE', 'NOT LIKE'], + 'parseBetween' => ['NOT BETWEEN', 'BETWEEN'], + 'parseIn' => ['NOT IN', 'IN'], + 'parseExp' => ['EXP'], + 'parseRegexp' => ['REGEXP', 'NOT REGEXP'], + 'parseNull' => ['NOT NULL', 'NULL'], + 'parseBetweenTime' => ['BETWEEN TIME', 'NOT BETWEEN TIME'], + 'parseTime' => ['< TIME', '> TIME', '<= TIME', '>= TIME'], + 'parseExists' => ['NOT EXISTS', 'EXISTS'], + 'parseColumn' => ['COLUMN'], + ]; + + protected $insertAllSql = '%INSERT% INTO %TABLE% (%FIELD%) VALUES %DATA% %COMMENT%'; + protected $updateSql = 'UPDATE %TABLE% %JOIN% SET %SET% %WHERE% %ORDER%%LIMIT% %LOCK%%COMMENT%'; + + /** + * 生成insertall SQL + * @access public + * @param Query $query 查询对象 + * @param array $dataSet 数据集 + * @param bool $replace 是否replace + * @return string + */ + public function insertAll(Query $query, $dataSet, $replace = false) + { + $options = $query->getOptions(); + + // 获取合法的字段 + if ('*' == $options['field']) { + $allowFields = $this->connection->getTableFields($options['table']); + } else { + $allowFields = $options['field']; + } + + // 获取绑定信息 + $bind = $this->connection->getFieldsBind($options['table']); + + foreach ($dataSet as $k => $data) { + $data = $this->parseData($query, $data, $allowFields, $bind, '_' . $k); + + $values[] = '( ' . implode(',', array_values($data)) . ' )'; + + if (!isset($insertFields)) { + $insertFields = array_keys($data); + } + } + + $fields = []; + foreach ($insertFields as $field) { + $fields[] = $this->parseKey($query, $field); + } + + return str_replace( + ['%INSERT%', '%TABLE%', '%FIELD%', '%DATA%', '%COMMENT%'], + [ + $replace ? 'REPLACE' : 'INSERT', + $this->parseTable($query, $options['table']), + implode(' , ', $fields), + implode(' , ', $values), + $this->parseComment($query, $options['comment']), + ], + $this->insertAllSql); + } + + /** + * 正则查询 + * @access protected + * @param Query $query 查询对象 + * @param string $key + * @param string $exp + * @param Expression $value + * @param string $field + * @return string + */ + protected function parseRegexp(Query $query, $key, $exp, Expression $value, $field) + { + return $key . ' ' . $exp . ' ' . $value->getValue(); + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('->', $key, 2); + + return 'json_extract(' . $this->parseKey($query, $field, true) . ', \'$.' . str_replace('->', '.', $name) . '\')'; + } elseif (strpos($key, '.') && !preg_match('/[,\'\"\(\)`\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)`.\s]/', $key))) { + $key = '`' . $key . '`'; + } + + if (isset($table)) { + if (strpos($table, '.')) { + $table = str_replace('.', '`.`', $table); + } + + $key = '`' . $table . '`.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'rand()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Pgsql.php b/thinkphp/library/think/db/builder/Pgsql.php new file mode 100644 index 0000000..742c7db --- /dev/null +++ b/thinkphp/library/think/db/builder/Pgsql.php @@ -0,0 +1,104 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Builder +{ + + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * limit分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, $limit) + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '->') && false === strpos($key, '(')) { + // JSON字段支持 + list($field, $name) = explode('->', $key); + $key = $field . '->>\'' . $name . '\''; + } elseif (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'RANDOM()'; + } + +} diff --git a/thinkphp/library/think/db/builder/Sqlite.php b/thinkphp/library/think/db/builder/Sqlite.php new file mode 100644 index 0000000..2b887ca --- /dev/null +++ b/thinkphp/library/think/db/builder/Sqlite.php @@ -0,0 +1,96 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Query; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Builder +{ + + /** + * limit + * @access public + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + public function parseLimit(Query $query, $limit) + { + $limitStr = ''; + + if (!empty($limit)) { + $limit = explode(',', $limit); + if (count($limit) > 1) { + $limitStr .= ' LIMIT ' . $limit[1] . ' OFFSET ' . $limit[0] . ' '; + } else { + $limitStr .= ' LIMIT ' . $limit[0] . ' '; + } + } + + return $limitStr; + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'RANDOM()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '.')) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if (isset($table)) { + $key = $table . '.' . $key; + } + + return $key; + } +} diff --git a/thinkphp/library/think/db/builder/Sqlsrv.php b/thinkphp/library/think/db/builder/Sqlsrv.php new file mode 100644 index 0000000..e24f7d2 --- /dev/null +++ b/thinkphp/library/think/db/builder/Sqlsrv.php @@ -0,0 +1,159 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\builder; + +use think\db\Builder; +use think\db\Expression; +use think\db\Query; +use think\Exception; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Builder +{ + protected $selectSql = 'SELECT T1.* FROM (SELECT thinkphp.*, ROW_NUMBER() OVER (%ORDER%) AS ROW_NUMBER FROM (SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%) AS thinkphp) AS T1 %LIMIT%%COMMENT%'; + protected $selectInsertSql = 'SELECT %DISTINCT% %FIELD% FROM %TABLE%%JOIN%%WHERE%%GROUP%%HAVING%'; + protected $updateSql = 'UPDATE %TABLE% SET %SET% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $deleteSql = 'DELETE FROM %TABLE% %USING% FROM %TABLE% %JOIN% %WHERE% %LIMIT% %LOCK%%COMMENT%'; + protected $insertSql = 'INSERT INTO %TABLE% (%FIELD%) VALUES (%DATA%) %COMMENT%'; + protected $insertAllSql = 'INSERT INTO %TABLE% (%FIELD%) %DATA% %COMMENT%'; + + /** + * order分析 + * @access protected + * @param Query $query 查询对象 + * @param mixed $order + * @return string + */ + protected function parseOrder(Query $query, $order) + { + if (empty($order)) { + return ' ORDER BY rand()'; + } + + foreach ($order as $key => $val) { + if ($val instanceof Expression) { + $array[] = $val->getValue(); + } elseif ('[rand]' == $val) { + $array[] = $this->parseRand($query); + } else { + if (is_numeric($key)) { + list($key, $sort) = explode(' ', strpos($val, ' ') ? $val : $val . ' '); + } else { + $sort = $val; + } + + if (preg_match('/^[\w\.]+$/', $key)) { + $sort = strtoupper($sort); + $sort = in_array($sort, ['ASC', 'DESC'], true) ? ' ' . $sort : ''; + $array[] = $this->parseKey($query, $key, true) . $sort; + } else { + throw new Exception('order express error:' . $key); + } + } + } + + return empty($array) ? '' : ' ORDER BY ' . implode(',', $array); + } + + /** + * 随机排序 + * @access protected + * @param Query $query 查询对象 + * @return string + */ + protected function parseRand(Query $query) + { + return 'rand()'; + } + + /** + * 字段和表名处理 + * @access public + * @param Query $query 查询对象 + * @param mixed $key 字段名 + * @param bool $strict 严格检测 + * @return string + */ + public function parseKey(Query $query, $key, $strict = false) + { + if (is_numeric($key)) { + return $key; + } elseif ($key instanceof Expression) { + return $key->getValue(); + } + + $key = trim($key); + + if (strpos($key, '.') && !preg_match('/[,\'\"\(\)\[\s]/', $key)) { + list($table, $key) = explode('.', $key, 2); + + $alias = $query->getOptions('alias'); + + if ('__TABLE__' == $table) { + $table = $query->getOptions('table'); + $table = is_array($table) ? array_shift($table) : $table; + } + + if (isset($alias[$table])) { + $table = $alias[$table]; + } + } + + if ($strict && !preg_match('/^[\w\.\*]+$/', $key)) { + throw new Exception('not support data:' . $key); + } + + if ('*' != $key && ($strict || !preg_match('/[,\'\"\*\(\)\[.\s]/', $key))) { + $key = '[' . $key . ']'; + } + + if (isset($table)) { + $key = '[' . $table . '].' . $key; + } + + return $key; + } + + /** + * limit + * @access protected + * @param Query $query 查询对象 + * @param mixed $limit + * @return string + */ + protected function parseLimit(Query $query, $limit) + { + if (empty($limit)) { + return ''; + } + + $limit = explode(',', $limit); + + if (count($limit) > 1) { + $limitStr = '(T1.ROW_NUMBER BETWEEN ' . $limit[0] . ' + 1 AND ' . $limit[0] . ' + ' . $limit[1] . ')'; + } else { + $limitStr = '(T1.ROW_NUMBER BETWEEN 1 AND ' . $limit[0] . ")"; + } + + return 'WHERE ' . $limitStr; + } + + public function selectInsert(Query $query, $fields, $table) + { + $this->selectSql = $this->selectInsertSql; + + return parent::selectInsert($query, $fields, $table); + } + +} diff --git a/thinkphp/library/think/db/connector/Mysql.php b/thinkphp/library/think/db/connector/Mysql.php new file mode 100644 index 0000000..93b8a18 --- /dev/null +++ b/thinkphp/library/think/db/connector/Mysql.php @@ -0,0 +1,209 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\db\Query; + +/** + * mysql数据库驱动 + */ +class Mysql extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Mysql'; + + /** + * 初始化 + * @access protected + * @return void + */ + protected function initialize() + { + // Point类型支持 + Query::extend('point', function ($query, $field, $value = null, $fun = 'GeomFromText', $type = 'POINT') { + if (!is_null($value)) { + $query->data($field, ['point', $value, $fun, $type]); + } else { + if (is_string($field)) { + $field = explode(',', $field); + } + $query->setOption('point', $field); + } + + return $query; + }); + } + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + if (!empty($config['socket'])) { + $dsn = 'mysql:unix_socket=' . $config['socket']; + } elseif (!empty($config['hostport'])) { + $dsn = 'mysql:host=' . $config['hostname'] . ';port=' . $config['hostport']; + } else { + $dsn = 'mysql:host=' . $config['hostname']; + } + $dsn .= ';dbname=' . $config['database']; + + if (!empty($config['charset'])) { + $dsn .= ';charset=' . $config['charset']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + + if (false === strpos($tableName, '`')) { + if (strpos($tableName, '.')) { + $tableName = str_replace('.', '`.`', $tableName); + } + $tableName = '`' . $tableName . '`'; + } + + $sql = 'SHOW COLUMNS FROM ' . $tableName; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' === $val['null']), // not null is empty, null is yes + 'default' => $val['default'], + 'primary' => (strtolower($val['key']) == 'pri'), + 'autoinc' => (strtolower($val['extra']) == 'auto_increment'), + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = !empty($dbName) ? 'SHOW TABLES FROM ' . $dbName : 'SHOW TABLES '; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + $pdo = $this->linkID->query("EXPLAIN " . $sql); + $result = $pdo->fetch(PDO::FETCH_ASSOC); + $result = array_change_key_case($result); + + if (isset($result['extra'])) { + if (strpos($result['extra'], 'filesort') || strpos($result['extra'], 'temporary')) { + $this->log('SQL:' . $this->queryStr . '[' . $result['extra'] . ']', 'warn'); + } + } + + return $result; + } + + protected function supportSavepoint() + { + return true; + } + + /** + * 启动XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function startTransXa($xid) + { + $this->initConnect(true); + if (!$this->linkID) { + return false; + } + + $this->execute("XA START '$xid'"); + } + + /** + * 预编译XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function prepareXa($xid) + { + $this->initConnect(true); + $this->execute("XA END '$xid'"); + $this->execute("XA PREPARE '$xid'"); + } + + /** + * 提交XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function commitXa($xid) + { + $this->initConnect(true); + $this->execute("XA COMMIT '$xid'"); + } + + /** + * 回滚XA事务 + * @access public + * @param string $xid XA事务id + * @return void + */ + public function rollbackXa($xid) + { + $this->initConnect(true); + $this->execute("XA ROLLBACK '$xid'"); + } +} diff --git a/thinkphp/library/think/db/connector/Pgsql.php b/thinkphp/library/think/db/connector/Pgsql.php new file mode 100644 index 0000000..ee9fca0 --- /dev/null +++ b/thinkphp/library/think/db/connector/Pgsql.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Pgsql数据库驱动 + */ +class Pgsql extends Connection +{ + protected $builder = '\\think\\db\\builder\\Pgsql'; + + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'pgsql:dbname=' . $config['database'] . ';host=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ';port=' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'select fields_name as "field",fields_type as "type",fields_not_null as "null",fields_key_name as "key",fields_default as "default",fields_default as "extra" from table_msg(\'' . $tableName . '\');'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['field']] = [ + 'name' => $val['field'], + 'type' => $val['type'], + 'notnull' => (bool) ('' !== $val['null']), + 'default' => $val['default'], + 'primary' => !empty($val['key']), + 'autoinc' => (0 === strpos($val['extra'], 'nextval(')), + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "select tablename as Tables_in_test from pg_tables where schemaname ='public'"; + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlite.php b/thinkphp/library/think/db/connector/Sqlite.php new file mode 100644 index 0000000..5b9b3fa --- /dev/null +++ b/thinkphp/library/think/db/connector/Sqlite.php @@ -0,0 +1,108 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; + +/** + * Sqlite数据库驱动 + */ +class Sqlite extends Connection +{ + + protected $builder = '\\think\\db\\builder\\Sqlite'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlite:' . $config['database']; + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $sql = 'PRAGMA table_info( ' . $tableName . ' )'; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['name']] = [ + 'name' => $val['name'], + 'type' => $val['type'], + 'notnull' => 1 === $val['notnull'], + 'default' => $val['dflt_value'], + 'primary' => '1' == $val['pk'], + 'autoinc' => '1' == $val['pk'], + ]; + } + } + + return $this->fieldCase($info); + } + + /** + * 取得数据库的表信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "SELECT name FROM sqlite_master WHERE type='table' " + . "UNION ALL SELECT name FROM sqlite_temp_master " + . "WHERE type='table' ORDER BY name"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } + + protected function supportSavepoint() + { + return true; + } +} diff --git a/thinkphp/library/think/db/connector/Sqlsrv.php b/thinkphp/library/think/db/connector/Sqlsrv.php new file mode 100644 index 0000000..123affb --- /dev/null +++ b/thinkphp/library/think/db/connector/Sqlsrv.php @@ -0,0 +1,235 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\connector; + +use PDO; +use think\db\Connection; +use think\db\Query; + +/** + * Sqlsrv数据库驱动 + */ +class Sqlsrv extends Connection +{ + // PDO连接参数 + protected $params = [ + PDO::ATTR_CASE => PDO::CASE_NATURAL, + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + PDO::ATTR_ORACLE_NULLS => PDO::NULL_NATURAL, + PDO::ATTR_STRINGIFY_FETCHES => false, + ]; + + protected $builder = '\\think\\db\\builder\\Sqlsrv'; + + /** + * 解析pdo连接的dsn信息 + * @access protected + * @param array $config 连接信息 + * @return string + */ + protected function parseDsn($config) + { + $dsn = 'sqlsrv:Database=' . $config['database'] . ';Server=' . $config['hostname']; + + if (!empty($config['hostport'])) { + $dsn .= ',' . $config['hostport']; + } + + return $dsn; + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $tableName + * @return array + */ + public function getFields($tableName) + { + list($tableName) = explode(' ', $tableName); + $tableNames = explode('.', $tableName); + $tableName = isset($tableNames[1]) ? $tableNames[1] : $tableNames[0]; + + $sql = "SELECT column_name, data_type, column_default, is_nullable + FROM information_schema.tables AS t + JOIN information_schema.columns AS c + ON t.table_catalog = c.table_catalog + AND t.table_schema = c.table_schema + AND t.table_name = c.table_name + WHERE t.table_name = '$tableName'"; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + if ($result) { + foreach ($result as $key => $val) { + $val = array_change_key_case($val); + $info[$val['column_name']] = [ + 'name' => $val['column_name'], + 'type' => $val['data_type'], + 'notnull' => (bool) ('' === $val['is_nullable']), // not null is empty, null is yes + 'default' => $val['column_default'], + 'primary' => false, + 'autoinc' => false, + ]; + } + } + + $sql = "SELECT column_name FROM information_schema.key_column_usage WHERE table_name='$tableName'"; + + // 调试开始 + $this->debug(true); + + $pdo = $this->linkID->query($sql); + + // 调试结束 + $this->debug(false, $sql); + + $result = $pdo->fetch(PDO::FETCH_ASSOC); + + if ($result) { + $info[$result['column_name']]['primary'] = true; + } + + return $this->fieldCase($info); + } + + /** + * 取得数据表的字段信息 + * @access public + * @param string $dbName + * @return array + */ + public function getTables($dbName = '') + { + $sql = "SELECT TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + "; + + $pdo = $this->query($sql, [], false, true); + $result = $pdo->fetchAll(PDO::FETCH_ASSOC); + $info = []; + + foreach ($result as $key => $val) { + $info[$key] = current($val); + } + + return $info; + } + + /** + * 得到某个列的数组 + * @access public + * @param Query $query 查询对象 + * @param string $field 字段名 多个字段用逗号分隔 + * @param string $key 索引 + * @return array + */ + public function column(Query $query, $field, $key = '') + { + $options = $query->getOptions(); + + if (empty($options['fetch_sql']) && !empty($options['cache'])) { + // 判断查询缓存 + $cache = $options['cache']; + + $guid = is_string($cache['key']) ? $cache['key'] : $this->getCacheKey($query, $field); + + $result = Container::get('cache')->get($guid); + + if (false !== $result) { + return $result; + } + } + + if (isset($options['field'])) { + $query->removeOption('field'); + } + + if (is_null($field)) { + $field = '*'; + } elseif ($key && '*' != $field) { + $field = $key . ',' . $field; + } + + if (is_string($field)) { + $field = array_map('trim', explode(',', $field)); + } + + $query->setOption('field', $field); + + // 生成查询SQL + $sql = $this->builder->select($query); + + $bind = $query->getBind(); + + if (!empty($options['fetch_sql'])) { + // 获取实际执行的SQL语句 + return $this->getRealSql($sql, $bind); + } + + // 执行查询操作 + $pdo = $this->query($sql, $bind, $options['master'], true); + + if (1 == $pdo->columnCount()) { + $result = $pdo->fetchAll(PDO::FETCH_COLUMN); + } else { + $resultSet = $pdo->fetchAll(PDO::FETCH_ASSOC); + + if ('*' == $field && $key) { + $result = array_column($resultSet, null, $key); + } elseif ($resultSet) { + $fields = array_keys($resultSet[0]); + $count = count($fields); + $key1 = array_shift($fields); + $key2 = $fields ? array_shift($fields) : ''; + $key = $key ?: $key1; + + if (strpos($key, '.')) { + list($alias, $key) = explode('.', $key); + } + + if (3 == $count) { + $column = $key2; + } elseif ($count < 3) { + $column = $key1; + } else { + $column = null; + } + + $result = array_column($resultSet, $column, $key); + } else { + $result = []; + } + } + + if (isset($cache) && isset($guid)) { + // 缓存数据 + $this->cacheData($guid, $result, $cache); + } + + return $result; + } + + /** + * SQL性能分析 + * @access protected + * @param string $sql + * @return array + */ + protected function getExplain($sql) + { + return []; + } +} diff --git a/thinkphp/library/think/db/connector/pgsql.sql b/thinkphp/library/think/db/connector/pgsql.sql new file mode 100644 index 0000000..e1a09a3 --- /dev/null +++ b/thinkphp/library/think/db/connector/pgsql.sql @@ -0,0 +1,117 @@ +CREATE OR REPLACE FUNCTION pgsql_type(a_type varchar) RETURNS varchar AS +$BODY$ +DECLARE + v_type varchar; +BEGIN + IF a_type='int8' THEN + v_type:='bigint'; + ELSIF a_type='int4' THEN + v_type:='integer'; + ELSIF a_type='int2' THEN + v_type:='smallint'; + ELSIF a_type='bpchar' THEN + v_type:='char'; + ELSE + v_type:=a_type; + END IF; + RETURN v_type; +END; +$BODY$ +LANGUAGE PLPGSQL; + +CREATE TYPE "public"."tablestruct" AS ( + "fields_key_name" varchar(100), + "fields_name" VARCHAR(200), + "fields_type" VARCHAR(20), + "fields_length" BIGINT, + "fields_not_null" VARCHAR(10), + "fields_default" VARCHAR(500), + "fields_comment" VARCHAR(1000) +); + +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_schema_name varchar, a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; + v_oid oid; + v_sql varchar; + v_rec RECORD; + v_key varchar; +BEGIN + SELECT + pg_class.oid INTO v_oid + FROM + pg_class + INNER JOIN pg_namespace ON (pg_class.relnamespace = pg_namespace.oid AND lower(pg_namespace.nspname) = a_schema_name) + WHERE + pg_class.relname=a_table_name; + IF NOT FOUND THEN + RETURN; + END IF; + + v_sql=' + SELECT + pg_attribute.attname AS fields_name, + pg_attribute.attnum AS fields_index, + pgsql_type(pg_type.typname::varchar) AS fields_type, + pg_attribute.atttypmod-4 as fields_length, + CASE WHEN pg_attribute.attnotnull THEN ''not null'' + ELSE '''' + END AS fields_not_null, + pg_attrdef.adsrc AS fields_default, + pg_description.description AS fields_comment + FROM + pg_attribute + INNER JOIN pg_class ON pg_attribute.attrelid = pg_class.oid + INNER JOIN pg_type ON pg_attribute.atttypid = pg_type.oid + LEFT OUTER JOIN pg_attrdef ON pg_attrdef.adrelid = pg_class.oid AND pg_attrdef.adnum = pg_attribute.attnum + LEFT OUTER JOIN pg_description ON pg_description.objoid = pg_class.oid AND pg_description.objsubid = pg_attribute.attnum + WHERE + pg_attribute.attnum > 0 + AND attisdropped <> ''t'' + AND pg_class.oid = ' || v_oid || ' + ORDER BY pg_attribute.attnum' ; + + FOR v_rec IN EXECUTE v_sql LOOP + v_ret.fields_name=v_rec.fields_name; + v_ret.fields_type=v_rec.fields_type; + IF v_rec.fields_length > 0 THEN + v_ret.fields_length:=v_rec.fields_length; + ELSE + v_ret.fields_length:=NULL; + END IF; + v_ret.fields_not_null=v_rec.fields_not_null; + v_ret.fields_default=v_rec.fields_default; + v_ret.fields_comment=v_rec.fields_comment; + SELECT constraint_name INTO v_key FROM information_schema.key_column_usage WHERE table_schema=a_schema_name AND table_name=a_table_name AND column_name=v_rec.fields_name; + IF FOUND THEN + v_ret.fields_key_name=v_key; + ELSE + v_ret.fields_key_name=''; + END IF; + RETURN NEXT v_ret; + END LOOP; + RETURN ; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_schema_name varchar, a_table_name varchar) +IS '获得表信息'; + +---重载一个函数 +CREATE OR REPLACE FUNCTION "public"."table_msg" (a_table_name varchar) RETURNS SETOF "public"."tablestruct" AS +$body$ +DECLARE + v_ret tablestruct; +BEGIN + FOR v_ret IN SELECT * FROM table_msg('public',a_table_name) LOOP + RETURN NEXT v_ret; + END LOOP; + RETURN; +END; +$body$ +LANGUAGE 'plpgsql' VOLATILE CALLED ON NULL INPUT SECURITY INVOKER; + +COMMENT ON FUNCTION "public"."table_msg"(a_table_name varchar) +IS '获得表信息'; \ No newline at end of file diff --git a/thinkphp/library/think/db/exception/BindParamException.php b/thinkphp/library/think/db/exception/BindParamException.php new file mode 100644 index 0000000..dce0c7b --- /dev/null +++ b/thinkphp/library/think/db/exception/BindParamException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +/** + * PDO参数绑定异常 + */ +class BindParamException extends DbException +{ + + /** + * BindParamException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param array $bind + * @param int $code + */ + public function __construct($message, $config, $sql, $bind, $code = 10502) + { + $this->setData('Bind Param', $bind); + parent::__construct($message, $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/db/exception/DataNotFoundException.php b/thinkphp/library/think/db/exception/DataNotFoundException.php new file mode 100644 index 0000000..883e333 --- /dev/null +++ b/thinkphp/library/think/db/exception/DataNotFoundException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class DataNotFoundException extends DbException +{ + protected $table; + + /** + * DbException constructor. + * @access public + * @param string $message + * @param string $table + * @param array $config + */ + public function __construct($message, $table = '', array $config = []) + { + $this->message = $message; + $this->table = $table; + + $this->setData('Database Config', $config); + } + + /** + * 获取数据表名 + * @access public + * @return string + */ + public function getTable() + { + return $this->table; + } +} diff --git a/thinkphp/library/think/db/exception/ModelNotFoundException.php b/thinkphp/library/think/db/exception/ModelNotFoundException.php new file mode 100644 index 0000000..ae52baf --- /dev/null +++ b/thinkphp/library/think/db/exception/ModelNotFoundException.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- + +namespace think\db\exception; + +use think\exception\DbException; + +class ModelNotFoundException extends DbException +{ + protected $model; + + /** + * 构造方法 + * @access public + * @param string $message + * @param string $model + * @param array $config + */ + public function __construct($message, $model = '', array $config = []) + { + $this->message = $message; + $this->model = $model; + + $this->setData('Database Config', $config); + } + + /** + * 获取模型类名 + * @access public + * @return string + */ + public function getModel() + { + return $this->model; + } + +} diff --git a/thinkphp/library/think/debug/Console.php b/thinkphp/library/think/debug/Console.php new file mode 100644 index 0000000..5cbaa0f --- /dev/null +++ b/thinkphp/library/think/debug/Console.php @@ -0,0 +1,156 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Container; +use think\Db; +use think\Response; + +/** + * 浏览器调试输出 + */ +class Console +{ + protected $config = [ + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct($config = []) + { + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Container::get('request'); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 页面Trace信息 + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Container::get('debug')->getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + + //输出到控制台 + $lines = ''; + foreach ($trace as $type => $msg) { + $lines .= $this->console($type, $msg); + } + $js = << +{$lines} + +JS; + return $js; + } + + protected function console($type, $msg) + { + $type = strtolower($type); + $trace_tabs = array_values($this->config['tabs']); + $line[] = ($type == $trace_tabs[0] || '调试' == $type || '错误' == $type) + ? "console.group('{$type}');" + : "console.groupCollapsed('{$type}');"; + + foreach ((array) $msg as $key => $m) { + switch ($type) { + case '调试': + $var_type = gettype($m); + if (in_array($var_type, ['array', 'string'])) { + $line[] = "console.log(" . json_encode($m) . ");"; + } else { + $line[] = "console.log(" . json_encode(var_export($m, 1)) . ");"; + } + break; + case '错误': + $msg = str_replace("\n", '\n', addslashes(is_scalar($m) ? $m : json_encode($m))); + $style = 'color:#F4006B;font-size:14px;'; + $line[] = "console.error(\"%c{$msg}\", \"{$style}\");"; + break; + case 'sql': + $msg = str_replace("\n", '\n', addslashes($m)); + $style = "color:#009bb4;"; + $line[] = "console.log(\"%c{$msg}\", \"{$style}\");"; + break; + default: + $m = is_string($key) ? $key . ' ' . $m : $key + 1 . ' ' . $m; + $msg = json_encode($m); + $line[] = "console.log({$msg});"; + break; + } + } + $line[] = "console.groupEnd();"; + return implode(PHP_EOL, $line); + } + +} diff --git a/thinkphp/library/think/debug/Html.php b/thinkphp/library/think/debug/Html.php new file mode 100644 index 0000000..a123762 --- /dev/null +++ b/thinkphp/library/think/debug/Html.php @@ -0,0 +1,106 @@ + +// +---------------------------------------------------------------------- + +namespace think\debug; + +use think\Container; +use think\Db; +use think\Response; + +/** + * 页面Trace调试 + */ +class Html +{ + protected $config = [ + 'file' => '', + 'tabs' => ['base' => '基本', 'file' => '文件', 'info' => '流程', 'notice|error' => '错误', 'sql' => 'SQL', 'debug|log' => '调试'], + ]; + + // 实例化并传入参数 + public function __construct(array $config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 调试输出接口 + * @access public + * @param Response $response Response对象 + * @param array $log 日志信息 + * @return bool + */ + public function output(Response $response, array $log = []) + { + $request = Container::get('request'); + $contentType = $response->getHeader('Content-Type'); + $accept = $request->header('accept'); + if (strpos($accept, 'application/json') === 0 || $request->isAjax()) { + return false; + } elseif (!empty($contentType) && strpos($contentType, 'html') === false) { + return false; + } + // 获取基本信息 + $runtime = number_format(microtime(true) - Container::get('app')->getBeginTime(), 10, '.', ''); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $mem = number_format((memory_get_usage() - Container::get('app')->getBeginMem()) / 1024, 2); + + // 页面Trace信息 + if ($request->host()) { + $uri = $request->protocol() . ' ' . $request->method() . ' : ' . $request->url(true); + } else { + $uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + $base = [ + '请求信息' => date('Y-m-d H:i:s', $_SERVER['REQUEST_TIME']) . ' ' . $uri, + '运行时间' => number_format($runtime, 6) . 's [ 吞吐率:' . $reqs . 'req/s ] 内存消耗:' . $mem . 'kb 文件加载:' . count(get_included_files()), + '查询信息' => Db::$queryTimes . ' queries ' . Db::$executeTimes . ' writes ', + '缓存信息' => Container::get('cache')->getReadTimes() . ' reads,' . Container::get('cache')->getWriteTimes() . ' writes', + ]; + + if (session_id()) { + $base['会话信息'] = 'SESSION_ID=' . session_id(); + } + + $info = Container::get('debug')->getFile(true); + + // 页面Trace信息 + $trace = []; + foreach ($this->config['tabs'] as $name => $title) { + $name = strtolower($name); + switch ($name) { + case 'base': // 基本信息 + $trace[$title] = $base; + break; + case 'file': // 文件信息 + $trace[$title] = $info; + break; + default: // 调试信息 + if (strpos($name, '|')) { + // 多组信息 + $names = explode('|', $name); + $result = []; + foreach ($names as $name) { + $result = array_merge($result, isset($log[$name]) ? $log[$name] : []); + } + $trace[$title] = $result; + } else { + $trace[$title] = isset($log[$name]) ? $log[$name] : ''; + } + } + } + // 调用Trace页面模板 + ob_start(); + include $this->config['file']; + return ob_get_clean(); + } + +} diff --git a/thinkphp/library/think/exception/ClassNotFoundException.php b/thinkphp/library/think/exception/ClassNotFoundException.php new file mode 100644 index 0000000..eb22e73 --- /dev/null +++ b/thinkphp/library/think/exception/ClassNotFoundException.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ClassNotFoundException extends \RuntimeException +{ + protected $class; + public function __construct($message, $class = '') + { + $this->message = $message; + $this->class = $class; + } + + /** + * 获取类名 + * @access public + * @return string + */ + public function getClass() + { + return $this->class; + } +} diff --git a/thinkphp/library/think/exception/DbException.php b/thinkphp/library/think/exception/DbException.php new file mode 100644 index 0000000..0f50425 --- /dev/null +++ b/thinkphp/library/think/exception/DbException.php @@ -0,0 +1,44 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * Database相关异常处理类 + */ +class DbException extends Exception +{ + /** + * DbException constructor. + * @access public + * @param string $message + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct($message, array $config, $sql, $code = 10500) + { + $this->message = $message; + $this->code = $code; + + $this->setData('Database Status', [ + 'Error Code' => $code, + 'Error Message' => $message, + 'Error SQL' => $sql, + ]); + + unset($config['username'], $config['password']); + $this->setData('Database Config', $config); + } + +} diff --git a/thinkphp/library/think/exception/ErrorException.php b/thinkphp/library/think/exception/ErrorException.php new file mode 100644 index 0000000..3143b8f --- /dev/null +++ b/thinkphp/library/think/exception/ErrorException.php @@ -0,0 +1,56 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Exception; + +/** + * ThinkPHP错误异常 + * 主要用于封装 set_error_handler 和 register_shutdown_function 得到的错误 + * 除开从 think\Exception 继承的功能 + * 其他和PHP系统\ErrorException功能基本一样 + */ +class ErrorException extends Exception +{ + /** + * 用于保存错误级别 + * @var integer + */ + protected $severity; + + /** + * 错误异常构造函数 + * @access public + * @param integer $severity 错误级别 + * @param string $message 错误详细信息 + * @param string $file 出错文件路径 + * @param integer $line 出错行号 + */ + public function __construct($severity, $message, $file, $line) + { + $this->severity = $severity; + $this->message = $message; + $this->file = $file; + $this->line = $line; + $this->code = 0; + } + + /** + * 获取错误级别 + * @access public + * @return integer 错误级别 + */ + final public function getSeverity() + { + return $this->severity; + } +} diff --git a/thinkphp/library/think/exception/Handle.php b/thinkphp/library/think/exception/Handle.php new file mode 100644 index 0000000..02c85ec --- /dev/null +++ b/thinkphp/library/think/exception/Handle.php @@ -0,0 +1,306 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use Exception; +use think\console\Output; +use think\Container; +use think\Response; + +class Handle +{ + protected $render; + protected $ignoreReport = [ + '\\think\\exception\\HttpException', + ]; + + public function setRender($render) + { + $this->render = $render; + } + + /** + * Report or log an exception. + * + * @access public + * @param \Exception $exception + * @return void + */ + public function report(Exception $exception) + { + if (!$this->isIgnoreReport($exception)) { + // 收集异常数据 + if (Container::get('app')->isDebug()) { + $data = [ + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'code' => $this->getCode($exception), + ]; + $log = "[{$data['code']}]{$data['message']}[{$data['file']}:{$data['line']}]"; + } else { + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + $log = "[{$data['code']}]{$data['message']}"; + } + + if (Container::get('app')->config('log.record_trace')) { + $log .= "\r\n" . $exception->getTraceAsString(); + } + + Container::get('log')->record($log, 'error'); + } + } + + protected function isIgnoreReport(Exception $exception) + { + foreach ($this->ignoreReport as $class) { + if ($exception instanceof $class) { + return true; + } + } + + return false; + } + + /** + * Render an exception into an HTTP response. + * + * @access public + * @param \Exception $e + * @return Response + */ + public function render(Exception $e) + { + if ($this->render && $this->render instanceof \Closure) { + $result = call_user_func_array($this->render, [$e]); + + if ($result) { + return $result; + } + } + + if ($e instanceof HttpException) { + return $this->renderHttpException($e); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access public + * @param Output $output + * @param Exception $e + */ + public function renderForConsole(Output $output, Exception $e) + { + if (Container::get('app')->isDebug()) { + $output->setVerbosity(Output::VERBOSITY_DEBUG); + } + + $output->renderException($e); + } + + /** + * @access protected + * @param HttpException $e + * @return Response + */ + protected function renderHttpException(HttpException $e) + { + $status = $e->getStatusCode(); + $template = Container::get('app')->config('http_exception_template'); + + if (!Container::get('app')->isDebug() && !empty($template[$status])) { + return Response::create($template[$status], 'view', $status)->assign(['e' => $e]); + } else { + return $this->convertExceptionToResponse($e); + } + } + + /** + * @access protected + * @param Exception $exception + * @return Response + */ + protected function convertExceptionToResponse(Exception $exception) + { + // 收集异常数据 + if (Container::get('app')->isDebug()) { + // 调试模式,获取详细的错误信息 + $data = [ + 'name' => get_class($exception), + 'file' => $exception->getFile(), + 'line' => $exception->getLine(), + 'message' => $this->getMessage($exception), + 'trace' => $exception->getTrace(), + 'code' => $this->getCode($exception), + 'source' => $this->getSourceCode($exception), + 'datas' => $this->getExtendData($exception), + 'tables' => [ + 'GET Data' => $_GET, + 'POST Data' => $_POST, + 'Files' => $_FILES, + 'Cookies' => $_COOKIE, + 'Session' => isset($_SESSION) ? $_SESSION : [], + 'Server/Request Data' => $_SERVER, + 'Environment Variables' => $_ENV, + 'ThinkPHP Constants' => $this->getConst(), + ], + ]; + } else { + // 部署模式仅显示 Code 和 Message + $data = [ + 'code' => $this->getCode($exception), + 'message' => $this->getMessage($exception), + ]; + + if (!Container::get('app')->config('show_error_msg')) { + // 不显示详细错误信息 + $data['message'] = Container::get('app')->config('error_message'); + } + } + + //保留一层 + while (ob_get_level() > 1) { + ob_end_clean(); + } + + $data['echo'] = ob_get_clean(); + + ob_start(); + extract($data); + include Container::get('app')->config('exception_tmpl'); + + // 获取并清空缓存 + $content = ob_get_clean(); + $response = Response::create($content, 'html'); + + if ($exception instanceof HttpException) { + $statusCode = $exception->getStatusCode(); + $response->header($exception->getHeaders()); + } + + if (!isset($statusCode)) { + $statusCode = 500; + } + $response->code($statusCode); + + return $response; + } + + /** + * 获取错误编码 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param \Exception $exception + * @return integer 错误编码 + */ + protected function getCode(Exception $exception) + { + $code = $exception->getCode(); + + if (!$code && $exception instanceof ErrorException) { + $code = $exception->getSeverity(); + } + + return $code; + } + + /** + * 获取错误信息 + * ErrorException则使用错误级别作为错误编码 + * @access protected + * @param \Exception $exception + * @return string 错误信息 + */ + protected function getMessage(Exception $exception) + { + $message = $exception->getMessage(); + + if (PHP_SAPI == 'cli') { + return $message; + } + + $lang = Container::get('lang'); + + if (strpos($message, ':')) { + $name = strstr($message, ':', true); + $message = $lang->has($name) ? $lang->get($name) . strstr($message, ':') : $message; + } elseif (strpos($message, ',')) { + $name = strstr($message, ',', true); + $message = $lang->has($name) ? $lang->get($name) . ':' . substr(strstr($message, ','), 1) : $message; + } elseif ($lang->has($message)) { + $message = $lang->get($message); + } + + return $message; + } + + /** + * 获取出错文件内容 + * 获取错误的前9行和后9行 + * @access protected + * @param \Exception $exception + * @return array 错误文件内容 + */ + protected function getSourceCode(Exception $exception) + { + // 读取前9行和后9行 + $line = $exception->getLine(); + $first = ($line - 9 > 0) ? $line - 9 : 1; + + try { + $contents = file($exception->getFile()); + $source = [ + 'first' => $first, + 'source' => array_slice($contents, $first - 1, 19), + ]; + } catch (Exception $e) { + $source = []; + } + + return $source; + } + + /** + * 获取异常扩展信息 + * 用于非调试模式html返回类型显示 + * @access protected + * @param \Exception $exception + * @return array 异常类定义的扩展数据 + */ + protected function getExtendData(Exception $exception) + { + $data = []; + + if ($exception instanceof \think\Exception) { + $data = $exception->getData(); + } + + return $data; + } + + /** + * 获取常量列表 + * @access private + * @return array 常量列表 + */ + private static function getConst() + { + $const = get_defined_constants(true); + + return isset($const['user']) ? $const['user'] : []; + } +} diff --git a/thinkphp/library/think/exception/HttpException.php b/thinkphp/library/think/exception/HttpException.php new file mode 100644 index 0000000..01a27fc --- /dev/null +++ b/thinkphp/library/think/exception/HttpException.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class HttpException extends \RuntimeException +{ + private $statusCode; + private $headers; + + public function __construct($statusCode, $message = null, \Exception $previous = null, array $headers = [], $code = 0) + { + $this->statusCode = $statusCode; + $this->headers = $headers; + + parent::__construct($message, $code, $previous); + } + + public function getStatusCode() + { + return $this->statusCode; + } + + public function getHeaders() + { + return $this->headers; + } +} diff --git a/thinkphp/library/think/exception/HttpResponseException.php b/thinkphp/library/think/exception/HttpResponseException.php new file mode 100644 index 0000000..5297286 --- /dev/null +++ b/thinkphp/library/think/exception/HttpResponseException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +use think\Response; + +class HttpResponseException extends \RuntimeException +{ + /** + * @var Response + */ + protected $response; + + public function __construct(Response $response) + { + $this->response = $response; + } + + public function getResponse() + { + return $this->response; + } + +} diff --git a/thinkphp/library/think/exception/PDOException.php b/thinkphp/library/think/exception/PDOException.php new file mode 100644 index 0000000..25240b6 --- /dev/null +++ b/thinkphp/library/think/exception/PDOException.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +/** + * PDO异常处理类 + * 重新封装了系统的\PDOException类 + */ +class PDOException extends DbException +{ + /** + * PDOException constructor. + * @access public + * @param \PDOException $exception + * @param array $config + * @param string $sql + * @param int $code + */ + public function __construct(\PDOException $exception, array $config, $sql, $code = 10501) + { + $error = $exception->errorInfo; + + $this->setData('PDO Error Info', [ + 'SQLSTATE' => $error[0], + 'Driver Error Code' => isset($error[1]) ? $error[1] : 0, + 'Driver Error Message' => isset($error[2]) ? $error[2] : '', + ]); + + parent::__construct($exception->getMessage(), $config, $sql, $code); + } +} diff --git a/thinkphp/library/think/exception/RouteNotFoundException.php b/thinkphp/library/think/exception/RouteNotFoundException.php new file mode 100644 index 0000000..d22e3a6 --- /dev/null +++ b/thinkphp/library/think/exception/RouteNotFoundException.php @@ -0,0 +1,22 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class RouteNotFoundException extends HttpException +{ + + public function __construct() + { + parent::__construct(404, 'Route Not Found'); + } + +} diff --git a/thinkphp/library/think/exception/TemplateNotFoundException.php b/thinkphp/library/think/exception/TemplateNotFoundException.php new file mode 100644 index 0000000..4202069 --- /dev/null +++ b/thinkphp/library/think/exception/TemplateNotFoundException.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class TemplateNotFoundException extends \RuntimeException +{ + protected $template; + + public function __construct($message, $template = '') + { + $this->message = $message; + $this->template = $template; + } + + /** + * 获取模板文件 + * @access public + * @return string + */ + public function getTemplate() + { + return $this->template; + } +} diff --git a/thinkphp/library/think/exception/ThrowableError.php b/thinkphp/library/think/exception/ThrowableError.php new file mode 100644 index 0000000..87b6b9d --- /dev/null +++ b/thinkphp/library/think/exception/ThrowableError.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ThrowableError extends \ErrorException +{ + public function __construct(\Throwable $e) + { + + if ($e instanceof \ParseError) { + $message = 'Parse error: ' . $e->getMessage(); + $severity = E_PARSE; + } elseif ($e instanceof \TypeError) { + $message = 'Type error: ' . $e->getMessage(); + $severity = E_RECOVERABLE_ERROR; + } else { + $message = 'Fatal error: ' . $e->getMessage(); + $severity = E_ERROR; + } + + parent::__construct( + $message, + $e->getCode(), + $severity, + $e->getFile(), + $e->getLine() + ); + + $this->setTrace($e->getTrace()); + } + + protected function setTrace($trace) + { + $traceReflector = new \ReflectionProperty('Exception', 'trace'); + $traceReflector->setAccessible(true); + $traceReflector->setValue($this, $trace); + } +} diff --git a/thinkphp/library/think/exception/ValidateException.php b/thinkphp/library/think/exception/ValidateException.php new file mode 100644 index 0000000..e3f8437 --- /dev/null +++ b/thinkphp/library/think/exception/ValidateException.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- + +namespace think\exception; + +class ValidateException extends \RuntimeException +{ + protected $error; + + public function __construct($error, $code = 0) + { + $this->error = $error; + $this->message = is_array($error) ? implode("\n\r", $error) : $error; + $this->code = $code; + } + + /** + * 获取验证错误信息 + * @access public + * @return array|string + */ + public function getError() + { + return $this->error; + } +} diff --git a/thinkphp/library/think/facade/App.php b/thinkphp/library/think/facade/App.php new file mode 100644 index 0000000..b375aa0 --- /dev/null +++ b/thinkphp/library/think/facade/App.php @@ -0,0 +1,63 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\App + * @mixin \think\App + * @method \think\App bind(string $bind) static 绑定模块或者控制器 + * @method void initialize() static 初始化应用 + * @method void init(string $module='') static 初始化模块 + * @method \think\Response run() static 执行应用 + * @method \think\App dispatch(\think\route\Dispatch $dispatch) static 设置当前请求的调度信息 + * @method void log(mixed $log, string $type = 'info') static 记录调试信息 + * @method mixed config(string $name='') static 获取配置参数 + * @method \think\route\Dispatch routeCheck() static URL路由检测(根据PATH_INFO) + * @method \think\App routeMust(bool $must = false) static 设置应用的路由检测机制 + * @method \think\Model model(string $name = '', string $layer = 'model', bool $appendSuffix = false, string $common = 'common') static 实例化模型 + * @method object controller(string $name, string $layer = 'controller', bool $appendSuffix = false, string $empty = '') static 实例化控制器 + * @method \think\Validate validate(string $name = '', string $layer = 'validate', bool $appendSuffix = false, string $common = 'common') static 实例化验证器类 + * @method \think\db\Query db(mixed $config = [], mixed $name = false) static 数据库初始化 + * @method mixed action(string $url, $vars = [], $layer = 'controller', $appendSuffix = false) static 调用模块的操作方法 + * @method string parseClass(string $module, string $layer, string $name, bool $appendSuffix = false) static 解析应用类的类名 + * @method string version() static 获取框架版本 + * @method bool isDebug() static 是否为调试模式 + * @method string getModulePath() static 获取当前模块路径 + * @method void setModulePath(string $path) static 设置当前模块路径 + * @method string getRootPath() static 获取应用根目录 + * @method string getAppPath() static 获取应用类库目录 + * @method string getRuntimePath() static 获取应用运行时目录 + * @method string getThinkPath() static 获取核心框架目录 + * @method string getRoutePath() static 获取路由目录 + * @method string getConfigPath() static 获取应用配置目录 + * @method string getConfigExt() static 获取配置后缀 + * @method string setNamespace(string $namespace) static 设置应用类库命名空间 + * @method string getNamespace() static 获取应用类库命名空间 + * @method string getSuffix() static 是否启用类库后缀 + * @method float getBeginTime() static 获取应用开启时间 + * @method integer getBeginMem() static 获取应用初始内存占用 + * @method \think\Container container() static 获取容器实例 + */ +class App extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'app'; + } +} diff --git a/thinkphp/library/think/facade/Build.php b/thinkphp/library/think/facade/Build.php new file mode 100644 index 0000000..c051bea --- /dev/null +++ b/thinkphp/library/think/facade/Build.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Build + * @mixin \think\Build + * @method void run(array $build = [], string $namespace = 'app', bool $suffix = false) static 根据传入的build资料创建目录和文件 + * @method void module(string $module = '', array $list = [], string $namespace = 'app', bool $suffix = false) static 创建模块 + */ +class Build extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'build'; + } +} diff --git a/thinkphp/library/think/facade/Cache.php b/thinkphp/library/think/facade/Cache.php new file mode 100644 index 0000000..9743486 --- /dev/null +++ b/thinkphp/library/think/facade/Cache.php @@ -0,0 +1,45 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cache + * @mixin \think\Cache + * @method \think\cache\Driver connect(array $options = [], mixed $name = false) static 连接缓存 + * @method \think\cache\Driver init(array $options = []) static 初始化缓存 + * @method \think\cache\Driver store(string $name = '') static 切换缓存类型 + * @method bool has(string $name) static 判断缓存是否存在 + * @method mixed get(string $name, mixed $default = false) static 读取缓存 + * @method mixed pull(string $name) static 读取缓存并删除 + * @method mixed set(string $name, mixed $value, int $expire = null) static 设置缓存 + * @method mixed remember(string $name, mixed $value, int $expire = null) static 如果不存在则写入缓存 + * @method mixed inc(string $name, int $step = 1) static 自增缓存(针对数值缓存) + * @method mixed dec(string $name, int $step = 1) static 自减缓存(针对数值缓存) + * @method bool rm(string $name) static 删除缓存 + * @method bool clear(string $tag = null) static 清除缓存 + * @method mixed tag(string $name, mixed $keys = null, bool $overlay = false) static 缓存标签 + * @method object handler() static 返回句柄对象,可执行其它高级方法 + */ +class Cache extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cache'; + } +} diff --git a/thinkphp/library/think/facade/Config.php b/thinkphp/library/think/facade/Config.php new file mode 100644 index 0000000..8646d12 --- /dev/null +++ b/thinkphp/library/think/facade/Config.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Config + * @mixin \think\Config + * @method bool has(string $name) static 检测配置是否存在 + * @method array pull(string $name) static 获取一级配置 + * @method mixed get(string $name,mixed $default = null) static 获取配置参数 + * @method mixed set(string $name, mixed $value = null) static 设置配置参数 + * @method array reset(string $prefix ='') static 重置配置参数 + */ +class Config extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'config'; + } +} diff --git a/thinkphp/library/think/facade/Cookie.php b/thinkphp/library/think/facade/Cookie.php new file mode 100644 index 0000000..4d7cea2 --- /dev/null +++ b/thinkphp/library/think/facade/Cookie.php @@ -0,0 +1,39 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Cookie + * @mixin \think\Cookie + * @method void init(array $config = []) static 初始化 + * @method bool has(string $name,string $prefix = null) static 判断Cookie数据 + * @method mixed prefix(string $prefix = '') static 设置或者获取cookie作用域(前缀) + * @method mixed get(string $name,string $prefix = null) static Cookie获取 + * @method mixed set(string $name, mixed $value = null, mixed $option = null) static 设置Cookie + * @method void forever(string $name, mixed $value = null, mixed $option = null) static 永久保存Cookie数据 + * @method void delete(string $name, string $prefix = null) static Cookie删除 + * @method void clear($prefix = null) static Cookie清空 + */ +class Cookie extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'cookie'; + } +} diff --git a/thinkphp/library/think/facade/Debug.php b/thinkphp/library/think/facade/Debug.php new file mode 100644 index 0000000..df20086 --- /dev/null +++ b/thinkphp/library/think/facade/Debug.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Debug + * @mixin \think\Debug + * @method void remark(string $name, mixed $value = '') static 记录时间(微秒)和内存使用情况 + * @method int getRangeTime(string $start, string $end, mixed $dec = 6) static 统计某个区间的时间(微秒)使用情况 + * @method int getUseTime(int $dec = 6) static 统计从开始到统计时的时间(微秒)使用情况 + * @method string getThroughputRate(string $start, string $end, mixed $dec = 6) static 获取当前访问的吞吐率情况 + * @method string getRangeMem(string $start, string $end, mixed $dec = 2) static 记录区间的内存使用情况 + * @method int getUseMem(int $dec = 2) static 统计从开始到统计时的内存使用情况 + * @method string getMemPeak(string $start, string $end, mixed $dec = 2) static 统计区间的内存峰值情况 + * @method mixed getFile(bool $detail = false) static 获取文件加载信息 + * @method mixed dump(mixed $var, bool $echo = true, string $label = null, int $flags = ENT_SUBSTITUTE) static 浏览器友好的变量输出 + */ +class Debug extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'debug'; + } +} diff --git a/thinkphp/library/think/facade/Env.php b/thinkphp/library/think/facade/Env.php new file mode 100644 index 0000000..5d04724 --- /dev/null +++ b/thinkphp/library/think/facade/Env.php @@ -0,0 +1,34 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Env + * @mixin \think\Env + * @method void load(string $file) static 读取环境变量定义文件 + * @method mixed get(string $name = null, mixed $default = null) static 获取环境变量值 + * @method void set(mixed $env, string $value = null) static 设置环境变量值 + */ +class Env extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'env'; + } +} diff --git a/thinkphp/library/think/facade/Hook.php b/thinkphp/library/think/facade/Hook.php new file mode 100644 index 0000000..e9e1208 --- /dev/null +++ b/thinkphp/library/think/facade/Hook.php @@ -0,0 +1,37 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Hook + * @mixin \think\Hook + * @method \think\Hook alias(mixed $name, mixed $behavior = null) static 指定行为标识 + * @method void add(string $tag, mixed $behavior, bool $first = false) static 动态添加行为扩展到某个标签 + * @method void import(array $tags, bool $recursive = true) static 批量导入插件 + * @method array get(string $tag = '') static 获取插件信息 + * @method mixed listen(string $tag, mixed $params = null, bool $once = false) static 监听标签的行为 + * @method mixed exec(mixed $class, mixed $params = null) static 执行行为 + */ +class Hook extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'hook'; + } +} diff --git a/thinkphp/library/think/facade/Lang.php b/thinkphp/library/think/facade/Lang.php new file mode 100644 index 0000000..56c4777 --- /dev/null +++ b/thinkphp/library/think/facade/Lang.php @@ -0,0 +1,41 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Lang + * @mixin \think\Lang + * @method mixed range($range = '') static 设定当前的语言 + * @method mixed set(mixed $name, string $value = null, string $range = '') static 设置语言定义 + * @method array load(mixed $file, string $range = '') static 加载语言定义 + * @method mixed get(string $name = null, array $vars = [], string $range = '') static 获取语言定义 + * @method mixed has(string $name, string $range = '') static 获取语言定义 + * @method string detect() static 自动侦测设置获取语言选择 + * @method void saveToCookie(string $lang = null) static 设置当前语言到Cookie + * @method void setLangDetectVar(string $var) static 设置语言自动侦测的变量 + * @method void setLangCookieVar(string $var) static 设置语言的cookie保存变量 + * @method void setAllowLangList(array $list) static 设置允许的语言列表 + */ +class Lang extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'lang'; + } +} diff --git a/thinkphp/library/think/facade/Log.php b/thinkphp/library/think/facade/Log.php new file mode 100644 index 0000000..ddf851e --- /dev/null +++ b/thinkphp/library/think/facade/Log.php @@ -0,0 +1,49 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Log + * @mixin \think\Log + * @method \think\Log init(array $config = []) static 日志初始化 + * @method mixed getLog(string $type = '') static 获取日志信息 + * @method \think\Log record(mixed $msg, string $type = 'info', array $context = []) static 记录日志信息 + * @method \think\Log clear() static 清空日志信息 + * @method \think\Log key(string $key) static 当前日志记录的授权key + * @method bool check(array $config) static 检查日志写入权限 + * @method bool save() static 保存调试信息 + * @method void write(mixed $msg, string $type = 'info', bool $force = false) static 实时写入日志信息 + * @method void log(string $level,mixed $message, array $context = []) static 记录日志信息 + * @method void emergency(mixed $message, array $context = []) static 记录emergency信息 + * @method void alert(mixed $message, array $context = []) static 记录alert信息 + * @method void critical(mixed $message, array $context = []) static 记录critical信息 + * @method void error(mixed $message, array $context = []) static 记录error信息 + * @method void warning(mixed $message, array $context = []) static 记录warning信息 + * @method void notice(mixed $message, array $context = []) static 记录notice信息 + * @method void info(mixed $message, array $context = []) static 记录info信息 + * @method void debug(mixed $message, array $context = []) static 记录debug信息 + * @method void sql(mixed $message, array $context = []) static 记录sql信息 + */ +class Log extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'log'; + } +} diff --git a/thinkphp/library/think/facade/Middleware.php b/thinkphp/library/think/facade/Middleware.php new file mode 100644 index 0000000..5e4cac7 --- /dev/null +++ b/thinkphp/library/think/facade/Middleware.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Middleware + * @mixin \think\Middleware + * @method void import(array $middlewares = []) static 批量设置中间件 + * @method void add(mixed $middleware) static 添加中间件到队列 + * @method void unshift(mixed $middleware) static 添加中间件到队列开头 + * @method array all() static 获取中间件队列 + * @method \think\Response dispatch(\think\Request $request) static 执行中间件调度 + */ +class Middleware extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'middleware'; + } +} diff --git a/thinkphp/library/think/facade/Request.php b/thinkphp/library/think/facade/Request.php new file mode 100644 index 0000000..0989253 --- /dev/null +++ b/thinkphp/library/think/facade/Request.php @@ -0,0 +1,97 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Request + * @mixin \think\Request + * @method void hook(mixed $method, mixed $callback = null) static Hook 方法注入 + * @method \think\Request create(string $uri, string $method = 'GET', array $params = [], array $cookie = [], array $files = [], array $server = [], string $content = null) static 创建一个URL请求 + * @method mixed domain(bool $port = false) static 获取当前包含协议、端口的域名 + * @method mixed url(bool $domain = false) static 获取当前完整URL + * @method mixed baseUrl(bool $domain = false) static 获取当前URL + * @method mixed baseFile(bool $domain = false) static 获取当前执行的文件 + * @method mixed root(bool $domain = false) static 获取URL访问根地址 + * @method string rootUrl() static 获取URL访问根目录 + * @method string pathinfo() static 获取当前请求URL的pathinfo信息(含URL后缀) + * @method string path() static 获取当前请求URL的pathinfo信息(不含URL后缀) + * @method string ext() static 当前URL的访问后缀 + * @method float time(bool $float = false) static 获取当前请求的时间 + * @method mixed type() static 当前请求的资源类型 + * @method void mimeType(mixed $type, string $val = '') static 设置资源类型 + * @method string method(bool $method = false) static 当前的请求类型 + * @method bool isGet() static 是否为GET请求 + * @method bool isPost() static 是否为POST请求 + * @method bool isPut() static 是否为PUT请求 + * @method bool isDelete() static 是否为DELTE请求 + * @method bool isHead() static 是否为HEAD请求 + * @method bool isPatch() static 是否为PATCH请求 + * @method bool isOptions() static 是否为OPTIONS请求 + * @method bool isCli() static 是否为cli + * @method bool isCgi() static 是否为cgi + * @method mixed param(string $name = '', mixed $default = null, mixed $filter = '') static 获取当前请求的参数 + * @method mixed route(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取路由参数 + * @method mixed get(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取GET参数 + * @method mixed post(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取POST参数 + * @method mixed put(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取PUT参数 + * @method mixed delete(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取DELETE参数 + * @method mixed patch(string $name = '', mixed $default = null, mixed $filter = '') static 设置获取PATCH参数 + * @method mixed request(string $name = '', mixed $default = null, mixed $filter = '') static 获取request变量 + * @method mixed session(string $name = '', mixed $default = null, mixed $filter = '') static 获取session数据 + * @method mixed cookie(string $name = '', mixed $default = null, mixed $filter = '') static 获取cookie参数 + * @method mixed server(string $name = '', mixed $default = null, mixed $filter = '') static 获取server参数 + * @method mixed env(string $name = '', mixed $default = null, mixed $filter = '') static 获取环境变量 + * @method mixed file(string $name = '') static 获取上传的文件信息 + * @method mixed header(string $name = '', mixed $default = null) static 设置或者获取当前的Header + * @method mixed input(array $data,mixed $name = '', mixed $default = null, mixed $filter = '') static 获取变量 支持过滤和默认值 + * @method mixed filter(mixed $filter = null) static 设置或获取当前的过滤规则 + * @method mixed has(string $name, string $type = 'param', bool $checkEmpty = false) static 是否存在某个请求参数 + * @method mixed only(mixed $name, string $type = 'param') static 获取指定的参数 + * @method mixed except(mixed $name, string $type = 'param') static 排除指定参数获取 + * @method bool isSsl() static 当前是否ssl + * @method bool isAjax(bool $ajax = false) static 当前是否Ajax请求 + * @method bool isPjax(bool $pjax = false) static 当前是否Pjax请求 + * @method mixed ip(int $type = 0, bool $adv = true) static 获取客户端IP地址 + * @method bool isMobile() static 检测是否使用手机访问 + * @method string scheme() static 当前URL地址中的scheme参数 + * @method string query() static 当前请求URL地址中的query参数 + * @method string host(bool $stric = false) static 当前请求的host + * @method string port() static 当前请求URL地址中的port参数 + * @method string protocol() static 当前请求 SERVER_PROTOCOL + * @method string remotePort() static 当前请求 REMOTE_PORT + * @method string contentType() static 当前请求 HTTP_CONTENT_TYPE + * @method array routeInfo() static 获取当前请求的路由信息 + * @method array dispatch() static 获取当前请求的调度信息 + * @method string module() static 获取当前的模块名 + * @method string controller(bool $convert = false) static 获取当前的控制器名 + * @method string action(bool $convert = false) static 获取当前的操作名 + * @method string langset() static 获取当前的语言 + * @method string getContent() static 设置或者获取当前请求的content + * @method string getInput() static 获取当前请求的php://input + * @method string token(string $name = '__token__', mixed $type = 'md5') static 生成请求令牌 + * @method string cache(string $key, mixed $expire = null, array $except = [], string $tag = null) static 设置当前地址的请求缓存 + * @method string getCache() static 读取请求缓存设置 + */ +class Request extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'request'; + } +} diff --git a/thinkphp/library/think/facade/Response.php b/thinkphp/library/think/facade/Response.php new file mode 100644 index 0000000..d7de142 --- /dev/null +++ b/thinkphp/library/think/facade/Response.php @@ -0,0 +1,47 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Response + * @mixin \think\Response + * @method \think\response create(mixed $data = '', string $type = '', int $code = 200, array $header = [], array $options = []) static 创建Response对象 + * @method void send() static 发送数据到客户端 + * @method \think\Response options(mixed $options = []) static 输出的参数 + * @method \think\Response data(mixed $data) static 输出数据设置 + * @method \think\Response header(mixed $name, string $value = null) static 设置响应头 + * @method \think\Response content(mixed $content) static 设置页面输出内容 + * @method \think\Response code(int $code) static 发送HTTP状态 + * @method \think\Response lastModified(string $time) static LastModified + * @method \think\Response expires(string $time) static expires + * @method \think\Response eTag(string $eTag) static eTag + * @method \think\Response cacheControl(string $cache) static 页面缓存控制 + * @method \think\Response contentType(string $contentType, string $charset = 'utf-8') static 页面输出类型 + * @method mixed getHeader(string $name) static 获取头部信息 + * @method mixed getData() static 获取原始数据 + * @method mixed getContent() static 获取输出数据 + * @method int getCode() static 获取状态码 + */ +class Response extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'response'; + } +} diff --git a/thinkphp/library/think/facade/Route.php b/thinkphp/library/think/facade/Route.php new file mode 100644 index 0000000..77196df --- /dev/null +++ b/thinkphp/library/think/facade/Route.php @@ -0,0 +1,57 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Route + * @mixin \think\Route + * @method \think\route\Domain domain(mixed $name, mixed $rule = '', array $option = [], array $pattern = []) static 注册域名路由 + * @method \think\Route pattern(mixed $name, string $rule = '') static 注册变量规则 + * @method \think\Route option(mixed $name, mixed $value = '') static 注册路由参数 + * @method \think\Route bind(string $bind) static 设置路由绑定 + * @method mixed getBind(string $bind) static 读取路由绑定 + * @method \think\Route name(string $name) static 设置当前路由标识 + * @method mixed getName(string $name) static 读取路由标识 + * @method void setName(string $name) static 批量导入路由标识 + * @method void import(array $rules, string $type = '*') static 导入配置文件的路由规则 + * @method \think\route\RuleItem rule(string $rule, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由规则 + * @method void rules(array $rules, string $method = '*', array $option = [], array $pattern = []) static 批量注册路由规则 + * @method \think\route\RuleGroup group(string|array $name, mixed $route, string $method = '*', array $option = [], array $pattern = []) static 注册路由分组 + * @method \think\route\RuleItem any(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem get(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem post(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem put(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem delete(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\RuleItem patch(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册路由 + * @method \think\route\Resource resource(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册资源路由 + * @method \think\Route controller(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册控制器路由 + * @method \think\Route alias(string $rule, mixed $route, array $option = [], array $pattern = []) static 注册别名路由 + * @method \think\Route setMethodPrefix(mixed $method, string $prefix = '') static 设置不同请求类型下面的方法前缀 + * @method \think\Route rest(string $name, array $resource = []) static rest方法定义和修改 + * @method \think\Route\RuleItem miss(string $route, string $method = '*', array $option = []) static 注册未匹配路由规则后的处理 + * @method \think\Route\RuleItem auto(string $route) static 注册一个自动解析的URL路由 + * @method \think\Route\Dispatch check(string $url, string $depr = '/', bool $must = false, bool $completeMatch = false) static 检测URL路由 + */ +class Route extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'route'; + } +} diff --git a/thinkphp/library/think/facade/Session.php b/thinkphp/library/think/facade/Session.php new file mode 100644 index 0000000..fb9206a --- /dev/null +++ b/thinkphp/library/think/facade/Session.php @@ -0,0 +1,46 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Session + * @mixin \think\Session + * @method void init(array $config = []) static session初始化 + * @method bool has(string $name,string $prefix = null) static 判断session数据 + * @method mixed prefix(string $prefix = '') static 设置或者获取session作用域(前缀) + * @method mixed get(string $name = '',string $prefix = null) static session获取 + * @method mixed pull(string $name,string $prefix = null) static session获取并删除 + * @method void push(string $key, mixed $value) static 添加数据到一个session数组 + * @method void set(string $name, mixed $value , string $prefix = null) static 设置session数据 + * @method void flash(string $name, mixed $value = null) static session设置 下一次请求有效 + * @method void flush() static 清空当前请求的session数据 + * @method void delete(string $name, string $prefix = null) static 删除session数据 + * @method void clear($prefix = null) static 清空session数据 + * @method void start() static 启动session + * @method void destroy() static 销毁session + * @method void pause() static 暂停session + * @method void regenerate(bool $delete = false) static 重新生成session_id + */ +class Session extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'session'; + } +} diff --git a/thinkphp/library/think/facade/Template.php b/thinkphp/library/think/facade/Template.php new file mode 100644 index 0000000..f91b118 --- /dev/null +++ b/thinkphp/library/think/facade/Template.php @@ -0,0 +1,36 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Template + * @mixin \think\Template + * @method void assign(mixed $name, mixed $value = '') static 模板变量赋值 + * @method mixed get(string $name = '') static 获取模板变量 + * @method void fetch(string $template, array $vars = [], array $config = []) static 渲染模板文件 + * @method void display(string $content, array $vars = [], array $config = []) static 渲染模板内容 + * @method mixed layout(string $name, string $replace = '') static 设置模板布局 + */ +class Template extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'template'; + } +} diff --git a/thinkphp/library/think/facade/Url.php b/thinkphp/library/think/facade/Url.php new file mode 100644 index 0000000..639591a --- /dev/null +++ b/thinkphp/library/think/facade/Url.php @@ -0,0 +1,33 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Url + * @mixin \think\Url + * @method string build(string $url = '', mixed $vars = '', mixed $suffix = true, mixed $domain = false) static URL生成 支持路由反射 + * @method void root(string $root) static 指定当前生成URL地址的root + */ +class Url extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'url'; + } +} diff --git a/thinkphp/library/think/facade/Validate.php b/thinkphp/library/think/facade/Validate.php new file mode 100644 index 0000000..a6eec23 --- /dev/null +++ b/thinkphp/library/think/facade/Validate.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\Validate + * @mixin \think\Validate + * @method \think\Validate make(array $rules = [], array $message = [], array $field = []) static 创建一个验证器类 + * @method \think\Validate rule(mixed $name, mixed $rule = '') static 添加字段验证规则 + * @method void extend(string $type, mixed $callback = null) static 注册扩展验证(类型)规则 + * @method void setTypeMsg(mixed $type, string $msg = null) static 设置验证规则的默认提示信息 + * @method \think\Validate message(mixed $name, string $message = '') static 设置提示信息 + * @method \think\Validate scene(string $name) static 设置验证场景 + * @method bool hasScene(string $name) static 判断是否存在某个验证场景 + * @method \think\Validate batch(bool $batch = true) static 设置批量验证 + * @method \think\Validate only(array $fields) static 指定需要验证的字段列表 + * @method \think\Validate remove(mixed $field, mixed $rule = true) static 移除某个字段的验证规则 + * @method \think\Validate append(mixed $field, mixed $rule = null) static 追加某个字段的验证规则 + * @method bool confirm(mixed $value, mixed $rule, array $data = [], string $field = '') static 验证是否和某个字段的值一致 + * @method bool different(mixed $value, mixed $rule, array $data = []) static 验证是否和某个字段的值是否不同 + * @method bool egt(mixed $value, mixed $rule, array $data = []) static 验证是否大于等于某个值 + * @method bool gt(mixed $value, mixed $rule, array $data = []) static 验证是否大于某个值 + * @method bool elt(mixed $value, mixed $rule, array $data = []) static 验证是否小于等于某个值 + * @method bool lt(mixed $value, mixed $rule, array $data = []) static 验证是否小于某个值 + * @method bool eq(mixed $value, mixed $rule) static 验证是否等于某个值 + * @method bool must(mixed $value, mixed $rule) static 必须验证 + * @method bool is(mixed $value, mixed $rule, array $data = []) static 验证字段值是否为有效格式 + * @method bool ip(mixed $value, mixed $rule) static 验证是否有效IP + * @method bool requireIf(mixed $value, mixed $rule) static 验证某个字段等于某个值的时候必须 + * @method bool requireCallback(mixed $value, mixed $rule,array $data) static 通过回调方法验证某个字段是否必须 + * @method bool requireWith(mixed $value, mixed $rule, array $data) static 验证某个字段有值的情况下必须 + * @method bool filter(mixed $value, mixed $rule) static 使用filter_var方式验证 + * @method bool in(mixed $value, mixed $rule) static 验证是否在范围内 + * @method bool notIn(mixed $value, mixed $rule) static 验证是否不在范围内 + * @method bool between(mixed $value, mixed $rule) static between验证数据 + * @method bool notBetween(mixed $value, mixed $rule) static 使用notbetween验证数据 + * @method bool length(mixed $value, mixed $rule) static 验证数据长度 + * @method bool max(mixed $value, mixed $rule) static 验证数据最大长度 + * @method bool min(mixed $value, mixed $rule) static 验证数据最小长度 + * @method bool after(mixed $value, mixed $rule) static 验证日期 + * @method bool before(mixed $value, mixed $rule) static 验证日期 + * @method bool expire(mixed $value, mixed $rule) static 验证有效期 + * @method bool allowIp(mixed $value, mixed $rule) static 验证IP许可 + * @method bool denyIp(mixed $value, mixed $rule) static 验证IP禁用 + * @method bool regex(mixed $value, mixed $rule) static 使用正则验证数据 + * @method bool token(mixed $value, mixed $rule) static 验证表单令牌 + * @method bool dateFormat(mixed $value, mixed $rule) static 验证时间和日期是否符合指定格式 + * @method bool unique(mixed $value, mixed $rule, array $data = [], string $field = '') static 验证是否唯一 + * @method bool check(array $data, mixed $rules = [], string $scene = '') static 数据自动验证 + * @method mixed getError(mixed $value, mixed $rule) static 获取错误信息 + */ +class Validate extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'validate'; + } + +} diff --git a/thinkphp/library/think/facade/View.php b/thinkphp/library/think/facade/View.php new file mode 100644 index 0000000..0843391 --- /dev/null +++ b/thinkphp/library/think/facade/View.php @@ -0,0 +1,40 @@ + +// +---------------------------------------------------------------------- + +namespace think\facade; + +use think\Facade; + +/** + * @see \think\View + * @mixin \think\View + * @method \think\View init(mixed $engine = [], array $replace = []) static 初始化 + * @method \think\View share(mixed $name, mixed $value = '') static 模板变量静态赋值 + * @method \think\View assign(mixed $name, mixed $value = '') static 模板变量赋值 + * @method \think\View config(mixed $name, mixed $value = '') static 配置模板引擎 + * @method \think\View exists(mixed $name) static 检查模板是否存在 + * @method \think\View filter(Callable $filter) static 视图内容过滤 + * @method \think\View engine(mixed $engine = []) static 设置当前模板解析的引擎 + * @method string fetch(string $template = '', array $vars = [], array $config = [], bool $renderContent = false) static 解析和获取模板内容 + * @method string display(string $content = '', array $vars = [], array $config = []) static 渲染内容输出 + */ +class View extends Facade +{ + /** + * 获取当前Facade对应类名(或者已经绑定的容器对象标识) + * @access protected + * @return string + */ + protected static function getFacadeClass() + { + return 'view'; + } +} diff --git a/thinkphp/library/think/log/driver/File.php b/thinkphp/library/think/log/driver/File.php new file mode 100644 index 0000000..10f745d --- /dev/null +++ b/thinkphp/library/think/log/driver/File.php @@ -0,0 +1,283 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * 本地化调试输出到文件 + */ +class File +{ + protected $config = [ + 'time_format' => ' c ', + 'single' => false, + 'file_size' => 2097152, + 'path' => '', + 'apart_level' => [], + 'max_files' => 0, + 'json' => false, + ]; + + protected $app; + + // 实例化并传入参数 + public function __construct(App $app, $config = []) + { + $this->app = $app; + + if (is_array($config)) { + $this->config = array_merge($this->config, $config); + } + + if (empty($this->config['path'])) { + $this->config['path'] = $this->app->getRuntimePath() . 'log' . DIRECTORY_SEPARATOR; + } elseif (substr($this->config['path'], -1) != DIRECTORY_SEPARATOR) { + $this->config['path'] .= DIRECTORY_SEPARATOR; + } + } + + /** + * 日志写入接口 + * @access public + * @param array $log 日志信息 + * @param bool $append 是否追加请求信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + $destination = $this->getMasterLogFile(); + + $path = dirname($destination); + !is_dir($path) && mkdir($path, 0755, true); + + $info = []; + + foreach ($log as $type => $val) { + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + + $info[$type][] = $this->config['json'] ? $msg : '[ ' . $type . ' ] ' . $msg; + } + + if (!$this->config['json'] && (true === $this->config['apart_level'] || in_array($type, $this->config['apart_level']))) { + // 独立记录的日志级别 + $filename = $this->getApartLevelFile($path, $type); + + $this->write($info[$type], $filename, true, $append); + + unset($info[$type]); + } + } + + if ($info) { + return $this->write($info, $destination, false, $append); + } + + return true; + } + + /** + * 日志写入 + * @access protected + * @param array $message 日志信息 + * @param string $destination 日志文件 + * @param bool $apart 是否独立文件写入 + * @param bool $append 是否追加请求信息 + * @return bool + */ + protected function write($message, $destination, $apart = false, $append = false) + { + // 检测日志文件大小,超过配置大小则备份日志文件重新生成 + $this->checkLogSize($destination); + + // 日志信息封装 + $info['timestamp'] = date($this->config['time_format']); + + foreach ($message as $type => $msg) { + $info[$type] = is_array($msg) ? implode("\r\n", $msg) : $msg; + } + + if (PHP_SAPI == 'cli') { + $message = $this->parseCliLog($info); + } else { + // 添加调试日志 + $this->getDebugLog($info, $append, $apart); + + $message = $this->parseLog($info); + } + + return error_log($message, 3, $destination); + } + + /** + * 获取主日志文件名 + * @access public + * @return string + */ + protected function getMasterLogFile() + { + if ($this->config['max_files']) { + $files = glob($this->config['path'] . '*.log'); + + try { + if (count($files) > $this->config['max_files']) { + unlink($files[0]); + } + } catch (\Exception $e) { + } + } + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $destination = $this->config['path'] . $name . '.log'; + } else { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['max_files']) { + $filename = date('Ymd') . $cli . '.log'; + } else { + $filename = date('Ym') . DIRECTORY_SEPARATOR . date('d') . $cli . '.log'; + } + + $destination = $this->config['path'] . $filename; + } + + return $destination; + } + + /** + * 获取独立日志文件名 + * @access public + * @param string $path 日志目录 + * @param string $type 日志类型 + * @return string + */ + protected function getApartLevelFile($path, $type) + { + $cli = PHP_SAPI == 'cli' ? '_cli' : ''; + + if ($this->config['single']) { + $name = is_string($this->config['single']) ? $this->config['single'] : 'single'; + + $name .= '_' . $type; + } elseif ($this->config['max_files']) { + $name = date('Ymd') . '_' . $type . $cli; + } else { + $name = date('d') . '_' . $type . $cli; + } + + return $path . DIRECTORY_SEPARATOR . $name . '.log'; + } + + /** + * 检查日志文件大小并自动生成备份文件 + * @access protected + * @param string $destination 日志文件 + * @return void + */ + protected function checkLogSize($destination) + { + if (is_file($destination) && floor($this->config['file_size']) <= filesize($destination)) { + try { + rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . time() . '-' . basename($destination)); + } catch (\Exception $e) { + } + } + } + + /** + * CLI日志解析 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseCliLog($info) + { + if ($this->config['json']) { + $message = json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } else { + $now = $info['timestamp']; + unset($info['timestamp']); + + $message = implode("\r\n", $info); + + $message = "[{$now}]" . $message . "\r\n"; + } + + return $message; + } + + /** + * 解析日志 + * @access protected + * @param array $info 日志信息 + * @return string + */ + protected function parseLog($info) + { + $requestInfo = [ + 'ip' => $this->app['request']->ip(), + 'method' => $this->app['request']->method(), + 'host' => $this->app['request']->host(), + 'uri' => $this->app['request']->url(), + ]; + + if ($this->config['json']) { + $info = $requestInfo + $info; + return json_encode($info, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . "\r\n"; + } + + array_unshift($info, "---------------------------------------------------------------\r\n[{$info['timestamp']}] {$requestInfo['ip']} {$requestInfo['method']} {$requestInfo['host']}{$requestInfo['uri']}"); + unset($info['timestamp']); + + return implode("\r\n", $info) . "\r\n"; + } + + protected function getDebugLog(&$info, $append, $apart) + { + if ($this->app->isDebug() && $append) { + + if ($this->config['json']) { + // 获取基本信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + + $info = [ + 'runtime' => number_format($runtime, 6) . 's', + 'reqs' => $reqs . 'req/s', + 'memory' => $memory_use . 'kb', + 'file' => count(get_included_files()), + ] + $info; + + } elseif (!$apart) { + // 增加额外的调试信息 + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + + $time_str = '[运行时间:' . number_format($runtime, 6) . 's] [吞吐率:' . $reqs . 'req/s]'; + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + array_unshift($info, $time_str . $memory_str . $file_load); + } + } + } +} diff --git a/thinkphp/library/think/log/driver/Socket.php b/thinkphp/library/think/log/driver/Socket.php new file mode 100644 index 0000000..5e4f8bf --- /dev/null +++ b/thinkphp/library/think/log/driver/Socket.php @@ -0,0 +1,279 @@ + +// +---------------------------------------------------------------------- + +namespace think\log\driver; + +use think\App; + +/** + * github: https://github.com/luofei614/SocketLog + * @author luofei614 + */ +class Socket +{ + public $port = 1116; //SocketLog 服务的http的端口号 + + protected $config = [ + // socket服务器地址 + 'host' => 'localhost', + // 是否显示加载的文件列表 + 'show_included_files' => false, + // 日志强制记录到配置的client_id + 'force_client_ids' => [], + // 限制允许读取日志的client_id + 'allow_client_ids' => [], + //输出到浏览器默认展开的日志级别 + 'expand_level' => ['debug'], + ]; + + protected $css = [ + 'sql' => 'color:#009bb4;', + 'sql_warn' => 'color:#009bb4;font-size:14px;', + 'error' => 'color:#f4006b;font-size:14px;', + 'page' => 'color:#40e2ff;background:#171717;', + 'big' => 'font-size:20px;color:red;', + ]; + + protected $allowForceClientIds = []; //配置强制推送且被授权的client_id + protected $app; + + /** + * 架构函数 + * @access public + * @param array $config 缓存参数 + */ + public function __construct(App $app, array $config = []) + { + $this->app = $app; + + if (!empty($config)) { + $this->config = array_merge($this->config, $config); + } + } + + /** + * 调试输出接口 + * @access public + * @param array $log 日志信息 + * @return bool + */ + public function save(array $log = [], $append = false) + { + if (!$this->check()) { + return false; + } + + $trace = []; + + if ($this->app->isDebug()) { + $runtime = round(microtime(true) - $this->app->getBeginTime(), 10); + $reqs = $runtime > 0 ? number_format(1 / $runtime, 2) : '∞'; + $time_str = ' [运行时间:' . number_format($runtime, 6) . 's][吞吐率:' . $reqs . 'req/s]'; + $memory_use = number_format((memory_get_usage() - $this->app->getBeginMem()) / 1024, 2); + $memory_str = ' [内存消耗:' . $memory_use . 'kb]'; + $file_load = ' [文件加载:' . count(get_included_files()) . ']'; + + if (isset($_SERVER['HTTP_HOST'])) { + $current_uri = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; + } else { + $current_uri = 'cmd:' . implode(' ', $_SERVER['argv']); + } + + // 基本信息 + $trace[] = [ + 'type' => 'group', + 'msg' => $current_uri . $time_str . $memory_str . $file_load, + 'css' => $this->css['page'], + ]; + } + + foreach ($log as $type => $val) { + $trace[] = [ + 'type' => in_array($type, $this->config['expand_level']) ? 'group' : 'groupCollapsed', + 'msg' => '[ ' . $type . ' ]', + 'css' => isset($this->css[$type]) ? $this->css[$type] : '', + ]; + + foreach ($val as $msg) { + if (!is_string($msg)) { + $msg = var_export($msg, true); + } + $trace[] = [ + 'type' => 'log', + 'msg' => $msg, + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + if ($this->config['show_included_files']) { + $trace[] = [ + 'type' => 'groupCollapsed', + 'msg' => '[ file ]', + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'log', + 'msg' => implode("\n", get_included_files()), + 'css' => '', + ]; + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + } + + $trace[] = [ + 'type' => 'groupEnd', + 'msg' => '', + 'css' => '', + ]; + + $tabid = $this->getClientArg('tabid'); + + if (!$client_id = $this->getClientArg('client_id')) { + $client_id = ''; + } + + if (!empty($this->allowForceClientIds)) { + //强制推送到多个client_id + foreach ($this->allowForceClientIds as $force_client_id) { + $client_id = $force_client_id; + $this->sendToClient($tabid, $client_id, $trace, $force_client_id); + } + } else { + $this->sendToClient($tabid, $client_id, $trace, ''); + } + + return true; + } + + /** + * 发送给指定客户端 + * @access protected + * @author Zjmainstay + * @param $tabid + * @param $client_id + * @param $logs + * @param $force_client_id + */ + protected function sendToClient($tabid, $client_id, $logs, $force_client_id) + { + $logs = [ + 'tabid' => $tabid, + 'client_id' => $client_id, + 'logs' => $logs, + 'force_client_id' => $force_client_id, + ]; + + $msg = @json_encode($logs); + $address = '/' . $client_id; //将client_id作为地址, server端通过地址判断将日志发布给谁 + + $this->send($this->config['host'], $msg, $address); + } + + protected function check() + { + $tabid = $this->getClientArg('tabid'); + + //是否记录日志的检查 + if (!$tabid && !$this->config['force_client_ids']) { + return false; + } + + //用户认证 + $allow_client_ids = $this->config['allow_client_ids']; + + if (!empty($allow_client_ids)) { + //通过数组交集得出授权强制推送的client_id + $this->allowForceClientIds = array_intersect($allow_client_ids, $this->config['force_client_ids']); + if (!$tabid && count($this->allowForceClientIds)) { + return true; + } + + $client_id = $this->getClientArg('client_id'); + if (!in_array($client_id, $allow_client_ids)) { + return false; + } + } else { + $this->allowForceClientIds = $this->config['force_client_ids']; + } + + return true; + } + + protected function getClientArg($name) + { + static $args = []; + + $key = 'HTTP_USER_AGENT'; + + if (isset($_SERVER['HTTP_SOCKETLOG'])) { + $key = 'HTTP_SOCKETLOG'; + } + + if (!isset($_SERVER[$key])) { + return; + } + + if (empty($args)) { + if (!preg_match('/SocketLog\((.*?)\)/', $_SERVER[$key], $match)) { + $args = ['tabid' => null]; + return; + } + parse_str($match[1], $args); + } + + if (isset($args[$name])) { + return $args[$name]; + } + + return; + } + + /** + * @access protected + * @param string $host - $host of socket server + * @param string $message - 发送的消息 + * @param string $address - 地址 + * @return bool + */ + protected function send($host, $message = '', $address = '/') + { + $url = 'http://' . $host . ':' . $this->port . $address; + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, $message); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + + $headers = [ + "Content-Type: application/json;charset=UTF-8", + ]; + + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置header + + return curl_exec($ch); + } + +} diff --git a/thinkphp/library/think/model/Collection.php b/thinkphp/library/think/model/Collection.php new file mode 100644 index 0000000..09b61a8 --- /dev/null +++ b/thinkphp/library/think/model/Collection.php @@ -0,0 +1,100 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Collection as BaseCollection; +use think\Model; + +class Collection extends BaseCollection +{ + /** + * 延迟预载入关联查询 + * @access public + * @param mixed $relation 关联 + * @return $this + */ + public function load($relation) + { + $item = current($this->items); + $item->eagerlyResultSet($this->items, $relation); + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden($hidden = [], $override = false) + { + $this->each(function ($model) use ($hidden, $override) { + /** @var Model $model */ + $model->hidden($hidden, $override); + }); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible($visible = [], $override = false) + { + $this->each(function ($model) use ($visible, $override) { + /** @var Model $model */ + $model->visible($visible, $override); + }); + + return $this; + } + + /** + * 设置需要追加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append($append = [], $override = false) + { + $this->each(function ($model) use ($append, $override) { + /** @var Model $model */ + $model && $model->append($append, $override); + }); + + return $this; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttr($name, $callback = null) + { + $this->each(function ($model) use ($name, $callback) { + /** @var Model $model */ + $model && $model->withAttribute($name, $callback); + }); + + return $this; + } +} diff --git a/thinkphp/library/think/model/Pivot.php b/thinkphp/library/think/model/Pivot.php new file mode 100644 index 0000000..a3a395e --- /dev/null +++ b/thinkphp/library/think/model/Pivot.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\Model; + +class Pivot extends Model +{ + + /** @var Model */ + public $parent; + + protected $autoWriteTimestamp = false; + + /** + * 架构函数 + * @access public + * @param array|object $data 数据 + * @param Model $parent 上级模型 + * @param string $table 中间数据表名 + */ + public function __construct($data = [], Model $parent = null, $table = '') + { + $this->parent = $parent; + + if (is_null($this->name)) { + $this->name = $table; + } + + parent::__construct($data); + } + +} diff --git a/thinkphp/library/think/model/Relation.php b/thinkphp/library/think/model/Relation.php new file mode 100644 index 0000000..c2b9adc --- /dev/null +++ b/thinkphp/library/think/model/Relation.php @@ -0,0 +1,166 @@ + +// +---------------------------------------------------------------------- + +namespace think\model; + +use think\db\Query; +use think\Exception; +use think\Model; + +/** + * Class Relation + * @package think\model + * + * @mixin Query + */ +abstract class Relation +{ + // 父模型对象 + protected $parent; + /** @var Model 当前关联的模型类 */ + protected $model; + /** @var Query 关联模型查询对象 */ + protected $query; + // 关联表外键 + protected $foreignKey; + // 关联表主键 + protected $localKey; + // 基础查询 + protected $baseQuery; + // 是否为自关联 + protected $selfRelation; + + /** + * 获取关联的所属模型 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + return $this->query->getModel(); + } + + /** + * 设置当前关联为自关联 + * @access public + * @param bool $self 是否自关联 + * @return $this + */ + public function selfRelation($self = true) + { + $this->selfRelation = $self; + return $this; + } + + /** + * 当前关联是否为自关联 + * @access public + * @return bool + */ + public function isSelfRelation() + { + return $this->selfRelation; + } + + /** + * 封装关联数据集 + * @access public + * @param array $resultSet 数据集 + * @return mixed + */ + protected function resultSetBuild($resultSet) + { + return (new $this->model)->toCollection($resultSet); + } + + protected function getQueryFields($model) + { + $fields = $this->query->getOptions('field'); + return $this->getRelationQueryFields($fields, $model); + } + + protected function getRelationQueryFields($fields, $model) + { + if ($fields) { + + if (is_string($fields)) { + $fields = explode(',', $fields); + } + + foreach ($fields as &$field) { + if (false === strpos($field, '.')) { + $field = $model . '.' . $field; + } + } + } else { + $fields = $model . '.*'; + } + + return $fields; + } + + protected function getQueryWhere(&$where, $relation) + { + foreach ($where as $key => &$val) { + if (is_string($key)) { + $where[] = [false === strpos($key, '.') ? $relation . '.' . $key : $key, '=', $val]; + unset($where[$key]); + } elseif (isset($val[0]) && false === strpos($val[0], '.')) { + $val[0] = $relation . '.' . $val[0]; + } + } + } + + /** + * 删除记录 + * @access public + * @param mixed $data 表达式 true 表示强制删除 + * @return int + * @throws Exception + * @throws PDOException + */ + public function delete($data = null) + { + return $this->query->delete($data); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + {} + + public function __call($method, $args) + { + if ($this->query) { + // 执行基础查询 + $this->baseQuery(); + + $result = call_user_func_array([$this->query->getModel(), $method], $args); + + return $result === $this->query && !in_array(strtolower($method), ['fetchsql', 'fetchpdo']) ? $this : $result; + } else { + throw new Exception('method not exists:' . __CLASS__ . '->' . $method); + } + } +} diff --git a/thinkphp/library/think/model/concern/Attribute.php b/thinkphp/library/think/model/concern/Attribute.php new file mode 100644 index 0000000..75b02ab --- /dev/null +++ b/thinkphp/library/think/model/concern/Attribute.php @@ -0,0 +1,649 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use InvalidArgumentException; +use think\Exception; +use think\Loader; +use think\model\Relation; + +trait Attribute +{ + /** + * 数据表主键 复合主键使用数组定义 + * @var string|array + */ + protected $pk = 'id'; + + /** + * 数据表字段信息 留空则自动获取 + * @var array + */ + protected $field = []; + + /** + * JSON数据表字段 + * @var array + */ + protected $json = []; + + /** + * JSON数据取出是否需要转换为数组 + * @var bool + */ + protected $jsonAssoc = false; + + /** + * JSON数据表字段类型 + * @var array + */ + protected $jsonType = []; + + /** + * 数据表废弃字段 + * @var array + */ + protected $disuse = []; + + /** + * 数据表只读字段 + * @var array + */ + protected $readonly = []; + + /** + * 数据表字段类型 + * @var array + */ + protected $type = []; + + /** + * 当前模型数据 + * @var array + */ + private $data = []; + + /** + * 修改器执行记录 + * @var array + */ + private $set = []; + + /** + * 原始数据 + * @var array + */ + private $origin = []; + + /** + * 动态获取器 + * @var array + */ + private $withAttr = []; + + /** + * 获取模型对象的主键 + * @access public + * @return string|array + */ + public function getPk() + { + return $this->pk; + } + + /** + * 判断一个字段名是否为主键字段 + * @access public + * @param string $key 名称 + * @return bool + */ + protected function isPk($key) + { + $pk = $this->getPk(); + if (is_string($pk) && $pk == $key) { + return true; + } elseif (is_array($pk) && in_array($key, $pk)) { + return true; + } + + return false; + } + + /** + * 获取模型对象的主键值 + * @access public + * @return integer + */ + public function getKey() + { + $pk = $this->getPk(); + if (is_string($pk) && array_key_exists($pk, $this->data)) { + return $this->data[$pk]; + } + + return; + } + + /** + * 设置允许写入的字段 + * @access public + * @param array|string|true $field 允许写入的字段 如果为true只允许写入数据表字段 + * @return $this + */ + public function allowField($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + + $this->field = $field; + + return $this; + } + + /** + * 设置只读字段 + * @access public + * @param array|string $field 只读字段 + * @return $this + */ + public function readonly($field) + { + if (is_string($field)) { + $field = explode(',', $field); + } + + $this->readonly = $field; + + return $this; + } + + /** + * 设置数据对象值 + * @access public + * @param mixed $data 数据或者属性名 + * @param mixed $value 值 + * @return $this + */ + public function data($data, $value = null) + { + if (is_string($data)) { + $this->data[$data] = $value; + return $this; + } + + // 清空数据 + $this->data = []; + + if (is_object($data)) { + $data = get_object_vars($data); + } + + if ($this->disuse) { + // 废弃字段 + foreach ((array) $this->disuse as $key) { + if (array_key_exists($key, $data)) { + unset($data[$key]); + } + } + } + + if (true === $value) { + // 数据对象赋值 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } elseif (is_array($value)) { + foreach ($value as $name) { + if (isset($data[$name])) { + $this->data[$name] = $data[$name]; + } + } + } else { + $this->data = $data; + } + + return $this; + } + + /** + * 批量设置数据对象值 + * @access public + * @param mixed $data 数据 + * @param bool $set 是否需要进行数据处理 + * @return $this + */ + public function appendData($data, $set = false) + { + if ($set) { + // 进行数据处理 + foreach ($data as $key => $value) { + $this->setAttr($key, $value, $data); + } + } else { + if (is_object($data)) { + $data = get_object_vars($data); + } + + $this->data = array_merge($this->data, $data); + } + + return $this; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回null + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + */ + public function getOrigin($name = null) + { + if (is_null($name)) { + return $this->origin; + } + return array_key_exists($name, $this->origin) ? $this->origin[$name] : null; + } + + /** + * 获取对象原始数据 如果不存在指定字段返回false + * @access public + * @param string $name 字段名 留空获取全部 + * @return mixed + * @throws InvalidArgumentException + */ + public function getData($name = null) + { + if (is_null($name)) { + return $this->data; + } elseif (array_key_exists($name, $this->data)) { + return $this->data[$name]; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 获取变化的数据 并排除只读数据 + * @access public + * @return array + */ + public function getChangedData() + { + if ($this->force) { + $data = $this->data; + } else { + $data = array_udiff_assoc($this->data, $this->origin, function ($a, $b) { + if ((empty($a) || empty($b)) && $a !== $b) { + return 1; + } + + return is_object($a) || $a != $b ? 1 : 0; + }); + } + + if (!empty($this->readonly)) { + // 只读字段不允许更新 + foreach ($this->readonly as $key => $field) { + if (isset($data[$field])) { + unset($data[$field]); + } + } + } + + return $data; + } + + /** + * 修改器 设置数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return void + */ + public function setAttr($name, $value, $data = []) + { + if (isset($this->set[$name])) { + return; + } + + if (is_null($value) && $this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + // 自动写入的时间戳字段 + $value = $this->autoWriteTimestamp($name); + } else { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + + $this->set[$name] = true; + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->writeTransform($value, $this->type[$name]); + } + } + + // 设置数据对象属性 + $this->data[$name] = $value; + } + + /** + * 是否需要自动写入时间字段 + * @access public + * @param bool $auto + * @return $this + */ + public function isAutoWriteTimestamp($auto) + { + $this->autoWriteTimestamp = $auto; + + return $this; + } + + /** + * 自动写入时间戳 + * @access protected + * @param string $name 时间戳字段 + * @return mixed + */ + protected function autoWriteTimestamp($name) + { + if (isset($this->type[$name])) { + $type = $this->type[$name]; + + if (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'datetime': + case 'date': + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format . '.u'); + break; + case 'timestamp': + case 'integer': + default: + $value = time(); + break; + } + } elseif (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat . '.u'); + } else { + $value = time(); + } + + return $value; + } + + /** + * 数据写入 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function writeTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_numeric($value)) { + $value = strtotime($value); + } + break; + case 'datetime': + $format = !empty($param) ? $param : $this->dateFormat; + $value = is_numeric($value) ? $value : strtotime($value); + $value = $this->formatDateTime($format, $value); + break; + case 'object': + if (is_object($value)) { + $value = json_encode($value, JSON_FORCE_OBJECT); + } + break; + case 'array': + $value = (array) $value; + case 'json': + $option = !empty($param) ? (int) $param : JSON_UNESCAPED_UNICODE; + $value = json_encode($value, $option); + break; + case 'serialize': + $value = serialize($value); + break; + } + + return $value; + } + + /** + * 获取器 获取数据对象的值 + * @access public + * @param string $name 名称 + * @param array $item 数据 + * @return mixed + * @throws InvalidArgumentException + */ + public function getAttr($name, &$item = null) + { + try { + $notFound = false; + $value = $this->getData($name); + } catch (InvalidArgumentException $e) { + $notFound = true; + $value = null; + } + + // 检测属性获取器 + $fieldName = Loader::parseName($name); + $method = 'get' . Loader::parseName($name, 1) . 'Attr'; + + if (isset($this->withAttr[$fieldName])) { + if ($notFound && $relation = $this->isRelationAttr($name)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + } + + $closure = $this->withAttr[$fieldName]; + $value = $closure($value, $this->data); + } elseif (method_exists($this, $method)) { + if ($notFound && $relation = $this->isRelationAttr($name)) { + $modelRelation = $this->$relation(); + $value = $this->getRelationData($modelRelation); + } + + $value = $this->$method($value, $this->data); + } elseif (isset($this->type[$name])) { + // 类型转换 + $value = $this->readTransform($value, $this->type[$name]); + } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) { + if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [ + 'datetime', + 'date', + 'timestamp', + ])) { + $value = $this->formatDateTime($this->dateFormat, $value); + } else { + $value = $this->formatDateTime($this->dateFormat, $value, true); + } + } elseif ($notFound) { + $value = $this->getRelationAttribute($name, $item); + } + + return $value; + } + + /** + * 获取关联属性值 + * @access protected + * @param string $name 属性名 + * @param array $item 数据 + * @return mixed + */ + protected function getRelationAttribute($name, &$item) + { + $relation = $this->isRelationAttr($name); + + if ($relation) { + $modelRelation = $this->$relation(); + if ($modelRelation instanceof Relation) { + $value = $this->getRelationData($modelRelation); + + if ($item && method_exists($modelRelation, 'getBindAttr') && $bindAttr = $modelRelation->getBindAttr()) { + + foreach ($bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + + if (isset($item[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $item[$key] = $value ? $value->getAttr($attr) : null; + } + } + + return false; + } + + // 保存关联对象值 + $this->relation[$name] = $value; + + return $value; + } + } + + throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name); + } + + /** + * 数据读取 类型转换 + * @access protected + * @param mixed $value 值 + * @param string|array $type 要转换的类型 + * @return mixed + */ + protected function readTransform($value, $type) + { + if (is_null($value)) { + return; + } + + if (is_array($type)) { + list($type, $param) = $type; + } elseif (strpos($type, ':')) { + list($type, $param) = explode(':', $type, 2); + } + + switch ($type) { + case 'integer': + $value = (int) $value; + break; + case 'float': + if (empty($param)) { + $value = (float) $value; + } else { + $value = (float) number_format($value, $param, '.', ''); + } + break; + case 'boolean': + $value = (bool) $value; + break; + case 'timestamp': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value, true); + } + break; + case 'datetime': + if (!is_null($value)) { + $format = !empty($param) ? $param : $this->dateFormat; + $value = $this->formatDateTime($format, $value); + } + break; + case 'json': + $value = json_decode($value, true); + break; + case 'array': + $value = empty($value) ? [] : json_decode($value, true); + break; + case 'object': + $value = empty($value) ? new \stdClass() : json_decode($value); + break; + case 'serialize': + try { + $value = unserialize($value); + } catch (\Exception $e) { + $value = null; + } + break; + default: + if (false !== strpos($type, '\\')) { + // 对象类型 + $value = new $type($value); + } + } + + return $value; + } + + /** + * 设置数据字段获取器 + * @access public + * @param string|array $name 字段名 + * @param callable $callback 闭包获取器 + * @return $this + */ + public function withAttribute($name, $callback = null) + { + if (is_array($name)) { + foreach ($name as $key => $val) { + $key = Loader::parseName($key); + + $this->withAttr[$key] = $val; + } + } else { + $name = Loader::parseName($name); + + $this->withAttr[$name] = $callback; + } + + return $this; + } +} diff --git a/thinkphp/library/think/model/concern/Conversion.php b/thinkphp/library/think/model/concern/Conversion.php new file mode 100644 index 0000000..b88528a --- /dev/null +++ b/thinkphp/library/think/model/concern/Conversion.php @@ -0,0 +1,288 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Collection; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Collection as ModelCollection; + +/** + * 模型数据转换处理 + */ +trait Conversion +{ + /** + * 数据输出显示的属性 + * @var array + */ + protected $visible = []; + + /** + * 数据输出隐藏的属性 + * @var array + */ + protected $hidden = []; + + /** + * 数据输出需要追加的属性 + * @var array + */ + protected $append = []; + + /** + * 数据集对象名 + * @var string + */ + protected $resultSetType; + + /** + * 设置需要附加的输出属性 + * @access public + * @param array $append 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function append(array $append = [], $override = false) + { + $this->append = $override ? $append : array_merge($this->append, $append); + + return $this; + } + + /** + * 设置附加关联对象的属性 + * @access public + * @param string $attr 关联属性 + * @param string|array $append 追加属性名 + * @return $this + * @throws Exception + */ + public function appendRelationAttr($attr, $append) + { + if (is_string($append)) { + $append = explode(',', $append); + } + + $relation = Loader::parseName($attr, 1, false); + if (isset($this->relation[$relation])) { + $model = $this->relation[$relation]; + } else { + $model = $this->getRelationData($this->$relation()); + } + + if ($model instanceof Model) { + foreach ($append as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($this->data[$key])) { + throw new Exception('bind attr has exists:' . $key); + } else { + $this->data[$key] = $model->$attr; + } + } + } + + return $this; + } + + /** + * 设置需要隐藏的输出属性 + * @access public + * @param array $hidden 属性列表 + * @param bool $override 是否覆盖 + * @return $this + */ + public function hidden(array $hidden = [], $override = false) + { + $this->hidden = $override ? $hidden : array_merge($this->hidden, $hidden); + + return $this; + } + + /** + * 设置需要输出的属性 + * @access public + * @param array $visible + * @param bool $override 是否覆盖 + * @return $this + */ + public function visible(array $visible = [], $override = false) + { + $this->visible = $override ? $visible : array_merge($this->visible, $visible); + + return $this; + } + + /** + * 转换当前模型对象为数组 + * @access public + * @return array + */ + public function toArray() + { + $item = []; + $visible = []; + $hidden = []; + + // 合并关联数据 + $data = array_merge($this->data, $this->relation); + + // 过滤属性 + if (!empty($this->visible)) { + $array = $this->parseAttr($this->visible, $visible); + $data = array_intersect_key($data, array_flip($array)); + } elseif (!empty($this->hidden)) { + $array = $this->parseAttr($this->hidden, $hidden, false); + $data = array_diff_key($data, array_flip($array)); + } + + foreach ($data as $key => $val) { + if ($val instanceof Model || $val instanceof ModelCollection) { + // 关联模型对象 + if (isset($visible[$key])) { + $val->visible($visible[$key]); + } elseif (isset($hidden[$key])) { + $val->hidden($hidden[$key]); + } + // 关联模型对象 + $item[$key] = $val->toArray(); + } else { + // 模型属性 + $item[$key] = $this->getAttr($key); + } + } + + // 追加属性(必须定义获取器) + if (!empty($this->append)) { + foreach ($this->append as $key => $name) { + if (is_array($name)) { + // 追加关联对象属性 + $relation = $this->getRelation($key); + + if (!$relation) { + $relation = $this->getAttr($key); + $relation->visible($name); + } + + $item[$key] = $relation->append($name)->toArray(); + } elseif (strpos($name, '.')) { + list($key, $attr) = explode('.', $name); + // 追加关联对象属性 + $relation = $this->getRelation($key); + + if (!$relation) { + $relation = $this->getAttr($key); + $relation->visible([$attr]); + } + + $item[$key] = $relation->append([$attr])->toArray(); + } else { + $value = $this->getAttr($name, $item); + if (false !== $value) { + $item[$name] = $value; + } + } + } + } + + return $item; + } + + /** + * 转换当前模型对象为JSON字符串 + * @access public + * @param integer $options json参数 + * @return string + */ + public function toJson($options = JSON_UNESCAPED_UNICODE) + { + return json_encode($this->toArray(), $options); + } + + /** + * 移除当前模型的关联属性 + * @access public + * @return $this + */ + public function removeRelation() + { + $this->relation = []; + return $this; + } + + public function __toString() + { + return $this->toJson(); + } + + // JsonSerializable + public function jsonSerialize() + { + return $this->toArray(); + } + + /** + * 转换数据集为数据集对象 + * @access public + * @param array|Collection $collection 数据集 + * @param string $resultSetType 数据集类 + * @return Collection + */ + public function toCollection($collection, $resultSetType = null) + { + $resultSetType = $resultSetType ?: $this->resultSetType; + + if ($resultSetType && false !== strpos($resultSetType, '\\')) { + $collection = new $resultSetType($collection); + } else { + $collection = new ModelCollection($collection); + } + + return $collection; + } + + /** + * 解析隐藏及显示属性 + * @access protected + * @param array $attrs 属性 + * @param array $result 结果集 + * @param bool $visible + * @return array + */ + protected function parseAttr($attrs, &$result, $visible = true) + { + $array = []; + + foreach ($attrs as $key => $val) { + if (is_array($val)) { + if ($visible) { + $array[] = $key; + } + + $result[$key] = $val; + } elseif (strpos($val, '.')) { + list($key, $name) = explode('.', $val); + + if ($visible) { + $array[] = $key; + } + + $result[$key][] = $name; + } else { + $array[] = $val; + } + } + + return $array; + } +} diff --git a/thinkphp/library/think/model/concern/ModelEvent.php b/thinkphp/library/think/model/concern/ModelEvent.php new file mode 100644 index 0000000..3a87484 --- /dev/null +++ b/thinkphp/library/think/model/concern/ModelEvent.php @@ -0,0 +1,238 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Container; +use think\Loader; + +/** + * 模型事件处理 + */ +trait ModelEvent +{ + /** + * 模型回调 + * @var array + */ + private static $event = []; + + /** + * 模型事件观察 + * @var array + */ + protected static $observe = ['before_write', 'after_write', 'before_insert', 'after_insert', 'before_update', 'after_update', 'before_delete', 'after_delete', 'before_restore', 'after_restore']; + + /** + * 绑定模型事件观察者类 + * @var array + */ + protected $observerClass; + + /** + * 是否需要事件响应 + * @var bool + */ + private $withEvent = true; + + /** + * 注册回调方法 + * @access public + * @param string $event 事件名 + * @param callable $callback 回调方法 + * @param bool $override 是否覆盖 + * @return void + */ + public static function event($event, $callback, $override = false) + { + $class = static::class; + + if ($override) { + self::$event[$class][$event] = []; + } + + self::$event[$class][$event][] = $callback; + } + + /** + * 清除回调方法 + * @access public + * @return void + */ + public static function flushEvent() + { + self::$event[static::class] = []; + } + + /** + * 注册一个模型观察者 + * + * @param object|string $class + * @return void + */ + public static function observe($class) + { + self::flushEvent(); + + foreach (static::$observe as $event) { + $eventFuncName = Loader::parseName($event, 1, false); + + if (method_exists($class, $eventFuncName)) { + static::event($event, [$class, $eventFuncName]); + } + } + } + + /** + * 当前操作的事件响应 + * @access protected + * @param bool $event 是否需要事件响应 + * @return $this + */ + public function withEvent($event) + { + $this->withEvent = $event; + return $this; + } + + /** + * 触发事件 + * @access protected + * @param string $event 事件名 + * @return bool + */ + protected function trigger($event) + { + $class = static::class; + + if ($this->withEvent && isset(self::$event[$class][$event])) { + foreach (self::$event[$class][$event] as $callback) { + $result = Container::getInstance()->invoke($callback, [$this]); + + if (false === $result) { + return false; + } + } + } + + return true; + } + + /** + * 模型before_insert事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeInsert($callback, $override = false) + { + self::event('before_insert', $callback, $override); + } + + /** + * 模型after_insert事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterInsert($callback, $override = false) + { + self::event('after_insert', $callback, $override); + } + + /** + * 模型before_update事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeUpdate($callback, $override = false) + { + self::event('before_update', $callback, $override); + } + + /** + * 模型after_update事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterUpdate($callback, $override = false) + { + self::event('after_update', $callback, $override); + } + + /** + * 模型before_write事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeWrite($callback, $override = false) + { + self::event('before_write', $callback, $override); + } + + /** + * 模型after_write事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterWrite($callback, $override = false) + { + self::event('after_write', $callback, $override); + } + + /** + * 模型before_delete事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeDelete($callback, $override = false) + { + self::event('before_delete', $callback, $override); + } + + /** + * 模型after_delete事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterDelete($callback, $override = false) + { + self::event('after_delete', $callback, $override); + } + + /** + * 模型before_restore事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function beforeRestore($callback, $override = false) + { + self::event('before_restore', $callback, $override); + } + + /** + * 模型after_restore事件快捷方法 + * @access protected + * @param callable $callback + * @param bool $override + */ + protected static function afterRestore($callback, $override = false) + { + self::event('after_restore', $callback, $override); + } +} diff --git a/thinkphp/library/think/model/concern/RelationShip.php b/thinkphp/library/think/model/concern/RelationShip.php new file mode 100644 index 0000000..38ad5d2 --- /dev/null +++ b/thinkphp/library/think/model/concern/RelationShip.php @@ -0,0 +1,670 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use think\Collection; +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; +use think\model\relation\BelongsTo; +use think\model\relation\BelongsToMany; +use think\model\relation\HasMany; +use think\model\relation\HasManyThrough; +use think\model\relation\HasOne; +use think\model\relation\MorphMany; +use think\model\relation\MorphOne; +use think\model\relation\MorphTo; + +/** + * 模型关联处理 + */ +trait RelationShip +{ + /** + * 父关联模型对象 + * @var object + */ + private $parent; + + /** + * 模型关联数据 + * @var array + */ + private $relation = []; + + /** + * 关联写入定义信息 + * @var array + */ + private $together; + + /** + * 关联自动写入信息 + * @var array + */ + protected $relationWrite; + + /** + * 设置父关联对象 + * @access public + * @param Model $model 模型对象 + * @return $this + */ + public function setParent($model) + { + $this->parent = $model; + + return $this; + } + + /** + * 获取父关联对象 + * @access public + * @return Model + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取当前模型的关联模型数据 + * @access public + * @param string $name 关联方法名 + * @return mixed + */ + public function getRelation($name = null) + { + if (is_null($name)) { + return $this->relation; + } elseif (array_key_exists($name, $this->relation)) { + return $this->relation[$name]; + } + return; + } + + /** + * 设置关联数据对象值 + * @access public + * @param string $name 属性名 + * @param mixed $value 属性值 + * @param array $data 数据 + * @return $this + */ + public function setRelation($name, $value, $data = []) + { + // 检测修改器 + $method = 'set' . Loader::parseName($name, 1) . 'Attr'; + + if (method_exists($this, $method)) { + $value = $this->$method($value, array_merge($this->data, $data)); + } + + $this->relation[$name] = $value; + + return $this; + } + + /** + * 关联数据写入 + * @access public + * @param array|string $relation 关联 + * @return $this + */ + public function together($relation) + { + if (is_string($relation)) { + $relation = explode(',', $relation); + } + + $this->together = $relation; + + $this->checkAutoRelationWrite(); + + return $this; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public static function has($relation, $operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $relation = (new static())->$relation(); + + if (is_array($operator) || $operator instanceof \Closure) { + return $relation->hasWhere($operator); + } + + return $relation->has($operator, $count, $id, $joinType); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $relation 关联方法名 + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public static function hasWhere($relation, $where = [], $fields = '*') + { + return (new static())->$relation()->hasWhere($where, $fields); + } + + /** + * 查询当前模型的关联数据 + * @access public + * @param string|array $relations 关联名 + * @param array $withRelationAttr 关联获取器 + * @return $this + */ + public function relationQuery($relations, $withRelationAttr = []) + { + if (is_string($relations)) { + $relations = explode(',', $relations); + } + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + // 支持闭包查询过滤关联条件 + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $method = Loader::parseName($relation, 1, false); + $relationName = Loader::parseName($relation); + + $relationResult = $this->$method(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $this->relation[$relation] = $relationResult->getRelation($subRelation, $closure); + } + + return $this; + } + + /** + * 预载入关联查询 返回数据集 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @return array + */ + public function eagerlyResultSet(&$resultSet, $relation, $withRelationAttr = [], $join = false) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $relation = Loader::parseName($relation, 1, false); + $relationName = Loader::parseName($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $relationResult->eagerlyResultSet($resultSet, $relation, $subRelation, $closure, $join); + } + } + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 关联名 + * @param array $withRelationAttr 关联获取器 + * @param bool $join 是否为JOIN方式 + * @return Model + */ + public function eagerlyResult(&$result, $relation, $withRelationAttr = [], $join = false) + { + $relations = is_string($relation) ? explode(',', $relation) : $relation; + + foreach ($relations as $key => $relation) { + $subRelation = ''; + $closure = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } + + if (is_array($relation)) { + $subRelation = $relation; + $relation = $key; + } elseif (strpos($relation, '.')) { + list($relation, $subRelation) = explode('.', $relation, 2); + } + + $relation = Loader::parseName($relation, 1, false); + $relationName = Loader::parseName($relation); + + $relationResult = $this->$relation(); + + if (isset($withRelationAttr[$relationName])) { + $relationResult->withAttr($withRelationAttr[$relationName]); + } + + $relationResult->eagerlyResult($result, $relation, $subRelation, $closure, $join); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param array $relations 关联名 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @return void + */ + public function relationCount(&$result, $relations, $aggregate = 'sum', $field = '*') + { + foreach ($relations as $key => $relation) { + $closure = $name = null; + + if ($relation instanceof \Closure) { + $closure = $relation; + $relation = $key; + } elseif (is_string($key)) { + $name = $relation; + $relation = $key; + } + + $relation = Loader::parseName($relation, 1, false); + + $count = $this->$relation()->relationCount($result, $closure, $aggregate, $field, $name); + + if (empty($name)) { + $name = Loader::parseName($relation) . '_' . $aggregate; + } + + $result->setAttr($name, $count); + } + } + + /** + * HAS ONE 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasOne + */ + public function hasOne($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasOne($this, $model, $foreignKey, $localKey); + } + + /** + * BELONGS TO 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @return BelongsTo + */ + public function belongsTo($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $foreignKey = $foreignKey ?: $this->getForeignKey((new $model)->getName()); + $localKey = $localKey ?: (new $model)->getPk(); + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + return new BelongsTo($this, $model, $foreignKey, $localKey, $relation); + } + + /** + * HAS MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前主键 + * @return HasMany + */ + public function hasMany($model, $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + + return new HasMany($this, $model, $foreignKey, $localKey); + } + + /** + * HAS MANY 远程关联定义 + * @access public + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + * @return HasManyThrough + */ + public function hasManyThrough($model, $through, $foreignKey = '', $throughKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $through = $this->parseModel($through); + $localKey = $localKey ?: $this->getPk(); + $foreignKey = $foreignKey ?: $this->getForeignKey($this->name); + $throughKey = $throughKey ?: $this->getForeignKey((new $through)->getName()); + + return new HasManyThrough($this, $model, $through, $foreignKey, $throughKey, $localKey); + } + + /** + * BELONGS TO MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型关联键 + * @return BelongsToMany + */ + public function belongsToMany($model, $table = '', $foreignKey = '', $localKey = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + $name = Loader::parseName(basename(str_replace('\\', '/', $model))); + $table = $table ?: Loader::parseName($this->name) . '_' . $name; + $foreignKey = $foreignKey ?: $name . '_id'; + $localKey = $localKey ?: $this->getForeignKey($this->name); + + return new BelongsToMany($this, $model, $table, $foreignKey, $localKey); + } + + /** + * MORPH One 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphOne + */ + public function morphOne($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + $type = $type ?: get_class($this); + + return new MorphOne($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH MANY 关联定义 + * @access public + * @param string $model 模型名 + * @param string|array $morph 多态字段信息 + * @param string $type 多态类型 + * @return MorphMany + */ + public function morphMany($model, $morph = null, $type = '') + { + // 记录当前关联信息 + $model = $this->parseModel($model); + + if (is_null($morph)) { + $trace = debug_backtrace(false, 2); + $morph = Loader::parseName($trace[1]['function']); + } + + $type = $type ?: get_class($this); + + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphMany($this, $model, $foreignKey, $morphType, $type); + } + + /** + * MORPH TO 关联定义 + * @access public + * @param string|array $morph 多态字段信息 + * @param array $alias 多态别名定义 + * @return MorphTo + */ + public function morphTo($morph = null, $alias = []) + { + $trace = debug_backtrace(false, 2); + $relation = Loader::parseName($trace[1]['function']); + + if (is_null($morph)) { + $morph = $relation; + } + + // 记录当前关联信息 + if (is_array($morph)) { + list($morphType, $foreignKey) = $morph; + } else { + $morphType = $morph . '_type'; + $foreignKey = $morph . '_id'; + } + + return new MorphTo($this, $morphType, $foreignKey, $alias, $relation); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (false === strpos($model, '\\')) { + $path = explode('\\', static::class); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 获取模型的默认外键名 + * @access protected + * @param string $name 模型名 + * @return string + */ + protected function getForeignKey($name) + { + if (strpos($name, '\\')) { + $name = basename(str_replace('\\', '/', $name)); + } + + return Loader::parseName($name) . '_id'; + } + + /** + * 检查属性是否为关联属性 如果是则返回关联方法名 + * @access protected + * @param string $attr 关联属性名 + * @return string|false + */ + protected function isRelationAttr($attr) + { + $relation = Loader::parseName($attr, 1, false); + + if (method_exists($this, $relation) && !method_exists('think\Model', $relation)) { + return $relation; + } + + return false; + } + + /** + * 智能获取关联模型数据 + * @access protected + * @param Relation $modelRelation 模型关联对象 + * @return mixed + */ + protected function getRelationData(Relation $modelRelation) + { + if ($this->parent && !$modelRelation->isSelfRelation() && get_class($this->parent) == get_class($modelRelation->getModel())) { + $value = $this->parent; + } else { + // 获取关联数据 + $value = $modelRelation->getRelation(); + } + + return $value; + } + + /** + * 关联数据自动写入检查 + * @access protected + * @return void + */ + protected function checkAutoRelationWrite() + { + foreach ($this->together as $key => $name) { + if (is_array($name)) { + if (key($name) === 0) { + $this->relationWrite[$key] = []; + // 绑定关联属性 + foreach ((array) $name as $val) { + if (isset($this->data[$val])) { + $this->relationWrite[$key][$val] = $this->data[$val]; + } + } + } else { + // 直接传入关联数据 + $this->relationWrite[$key] = $name; + } + } elseif (isset($this->relation[$name])) { + $this->relationWrite[$name] = $this->relation[$name]; + } elseif (isset($this->data[$name])) { + $this->relationWrite[$name] = $this->data[$name]; + unset($this->data[$name]); + } + } + } + + /** + * 自动关联数据更新(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationUpdate() + { + foreach ($this->relationWrite as $name => $val) { + if ($val instanceof Model) { + $val->isUpdate()->save(); + } else { + $model = $this->getRelation($name); + if ($model instanceof Model) { + $model->isUpdate()->save($val); + } + } + } + } + + /** + * 自动关联数据写入(针对一对一关联) + * @access protected + * @return void + */ + protected function autoRelationInsert() + { + foreach ($this->relationWrite as $name => $val) { + $method = Loader::parseName($name, 1, false); + $this->$method()->save($val); + } + } + + /** + * 自动关联数据删除(支持一对一及一对多关联) + * @access protected + * @return void + */ + protected function autoRelationDelete() + { + foreach ($this->relationWrite as $key => $name) { + $name = is_numeric($key) ? $name : $key; + $result = $this->getRelation($name); + + if ($result instanceof Model) { + $result->delete(); + } elseif ($result instanceof Collection) { + foreach ($result as $model) { + $model->delete(); + } + } + } + } +} diff --git a/thinkphp/library/think/model/concern/SoftDelete.php b/thinkphp/library/think/model/concern/SoftDelete.php new file mode 100644 index 0000000..7dc96e1 --- /dev/null +++ b/thinkphp/library/think/model/concern/SoftDelete.php @@ -0,0 +1,241 @@ +getDeleteTimeField(); + + if ($field && !empty($this->getOrigin($field))) { + return true; + } + + return false; + } + + /** + * 查询软删除数据 + * @access public + * @return Query + */ + public static function withTrashed() + { + $model = new static(); + + return $model->withTrashedData(true)->db(false); + } + + /** + * 是否包含软删除数据 + * @access protected + * @param bool $withTrashed 是否包含软删除数据 + * @return $this + */ + protected function withTrashedData($withTrashed) + { + $this->withTrashed = $withTrashed; + return $this; + } + + /** + * 只查询软删除数据 + * @access public + * @return Query + */ + public static function onlyTrashed() + { + $model = new static(); + $field = $model->getDeleteTimeField(true); + + if ($field) { + return $model + ->db(false) + ->useSoftDelete($field, $model->getWithTrashedExp()); + } + + return $model->db(false); + } + + /** + * 获取软删除数据的查询条件 + * @access protected + * @return array + */ + protected function getWithTrashedExp() + { + return is_null($this->defaultSoftDelete) ? + ['notnull', ''] : ['<>', $this->defaultSoftDelete]; + } + + /** + * 删除当前的记录 + * @access public + * @return bool + */ + public function delete($force = false) + { + if (!$this->isExists() || false === $this->trigger('before_delete', $this)) { + return false; + } + + $force = $force ?: $this->isForce(); + $name = $this->getDeleteTimeField(); + + if ($name && !$force) { + // 软删除 + $this->data($name, $this->autoWriteTimestamp($name)); + + $result = $this->isUpdate()->withEvent(false)->save(); + + $this->withEvent(true); + } else { + // 读取更新条件 + $where = $this->getWhere(); + + // 删除当前模型数据 + $result = $this->db(false) + ->where($where) + ->removeOption('soft_delete') + ->delete(); + } + + // 关联删除 + if (!empty($this->relationWrite)) { + $this->autoRelationDelete(); + } + + $this->trigger('after_delete', $this); + + $this->exists(false); + + return true; + } + + /** + * 删除记录 + * @access public + * @param mixed $data 主键列表 支持闭包查询条件 + * @param bool $force 是否强制删除 + * @return bool + */ + public static function destroy($data, $force = false) + { + // 包含软删除数据 + $query = (new static())->db(false); + + if (is_array($data) && key($data) !== 0) { + $query->where($data); + $data = null; + } elseif ($data instanceof \Closure) { + call_user_func_array($data, [ & $query]); + $data = null; + } elseif (is_null($data)) { + return false; + } + + $resultSet = $query->select($data); + + if ($resultSet) { + foreach ($resultSet as $data) { + $data->force($force)->delete(); + } + } + + return true; + } + + /** + * 恢复被软删除的记录 + * @access public + * @param array $where 更新条件 + * @return bool + */ + public function restore($where = []) + { + $name = $this->getDeleteTimeField(); + + if ($name) { + if (false === $this->trigger('before_restore')) { + return false; + } + + if (empty($where)) { + $pk = $this->getPk(); + + $where[] = [$pk, '=', $this->getData($pk)]; + } + + // 恢复删除 + $this->db(false) + ->where($where) + ->useSoftDelete($name, $this->getWithTrashedExp()) + ->update([$name => $this->defaultSoftDelete]); + + $this->trigger('after_restore'); + + return true; + } + + return false; + } + + /** + * 获取软删除字段 + * @access protected + * @param bool $read 是否查询操作 写操作的时候会自动去掉表别名 + * @return string|false + */ + protected function getDeleteTimeField($read = false) + { + $field = property_exists($this, 'deleteTime') && isset($this->deleteTime) ? $this->deleteTime : 'delete_time'; + + if (false === $field) { + return false; + } + + if (false === strpos($field, '.')) { + $field = '__TABLE__.' . $field; + } + + if (!$read && strpos($field, '.')) { + $array = explode('.', $field); + $field = array_pop($array); + } + + return $field; + } + + /** + * 查询的时候默认排除软删除数据 + * @access protected + * @param Query $query + * @return void + */ + protected function withNoTrashed($query) + { + $field = $this->getDeleteTimeField(true); + + if ($field) { + $query->useSoftDelete($field, $this->defaultSoftDelete); + } + } +} diff --git a/thinkphp/library/think/model/concern/TimeStamp.php b/thinkphp/library/think/model/concern/TimeStamp.php new file mode 100644 index 0000000..99a31fa --- /dev/null +++ b/thinkphp/library/think/model/concern/TimeStamp.php @@ -0,0 +1,92 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\concern; + +use DateTime; + +/** + * 自动时间戳 + */ +trait TimeStamp +{ + /** + * 是否需要自动写入时间戳 如果设置为字符串 则表示时间字段的类型 + * @var bool|string + */ + protected $autoWriteTimestamp; + + /** + * 创建时间字段 false表示关闭 + * @var false|string + */ + protected $createTime = 'create_time'; + + /** + * 更新时间字段 false表示关闭 + * @var false|string + */ + protected $updateTime = 'update_time'; + + /** + * 时间字段显示格式 + * @var string + */ + protected $dateFormat; + + /** + * 时间日期字段格式化处理 + * @access protected + * @param mixed $format 日期格式 + * @param mixed $time 时间日期表达式 + * @param bool $timestamp 是否进行时间戳转换 + * @return mixed + */ + protected function formatDateTime($format, $time = 'now', $timestamp = false) + { + if (empty($time)) { + return; + } + + if (false === $format) { + return $time; + } elseif (false !== strpos($format, '\\')) { + return new $format($time); + } + + if ($timestamp) { + $dateTime = new DateTime(); + $dateTime->setTimestamp($time); + } else { + $dateTime = new DateTime($time); + } + + return $dateTime->format($format); + } + + /** + * 检查时间字段写入 + * @access protected + * @return void + */ + protected function checkTimeStampWrite() + { + // 自动写入创建时间和更新时间 + if ($this->autoWriteTimestamp) { + if ($this->createTime && !isset($this->data[$this->createTime])) { + $this->data[$this->createTime] = $this->autoWriteTimestamp($this->createTime); + } + if ($this->updateTime && !isset($this->data[$this->updateTime])) { + $this->data[$this->updateTime] = $this->autoWriteTimestamp($this->updateTime); + } + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsTo.php b/thinkphp/library/think/model/relation/BelongsTo.php new file mode 100644 index 0000000..98d176e --- /dev/null +++ b/thinkphp/library/think/model/relation/BelongsTo.php @@ -0,0 +1,314 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Loader; +use think\Model; + +class BelongsTo extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 关联主键 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey, $relation = null) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = 'INNER'; + $this->query = (new $model)->db(); + $this->relation = $relation; + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + $closure($this->query); + } + + $foreignKey = $this->foreignKey; + + $relationModel = $this->query + ->removeWhereField($this->localKey) + ->where($this->localKey, $this->parent->$foreignKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->localKey, '=' . $this->parent->getTable() . '.' . $this->foreignKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $foreignKey = $this->foreignKey; + + if (!isset($result->$foreignKey)) { + return 0; + } + + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->localKey, '=', $result->$foreignKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation]) + ->field($relation . '.' . $localKey) + ->whereExp($model . '.' . $foreignKey, '=' . $relation . '.' . $localKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db() + ->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->foreignKey . '=' . $relation . '.' . $this->localKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$foreignKey)) { + $range[] = $result->$foreignKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, 'in', $range], + ], $localKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($localKey); + + $data = $this->eagerlyWhere([ + [$localKey, '=', $result->$foreignKey], + ], $localKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$foreignKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$foreignKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result); + } else { + // 设置关联属性 + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @return Model + */ + public function associate($model) + { + $this->parent->setAttr($this->foreignKey, $model->getKey()); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $this->parent->setAttr($this->foreignKey, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->foreignKey})) { + // 关联查询带入关联条件 + $this->query->where($this->localKey, '=', $this->parent->{$this->foreignKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/thinkphp/library/think/model/relation/BelongsToMany.php b/thinkphp/library/think/model/relation/BelongsToMany.php new file mode 100644 index 0000000..b7cdebe --- /dev/null +++ b/thinkphp/library/think/model/relation/BelongsToMany.php @@ -0,0 +1,707 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Collection; +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Pivot; +use think\model\Relation; + +class BelongsToMany extends Relation +{ + // 中间表表名 + protected $middle; + // 中间表模型名称 + protected $pivotName; + // 中间表数据名称 + protected $pivotDataName = 'pivot'; + // 中间表模型对象 + protected $pivot; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $table 中间表名 + * @param string $foreignKey 关联模型外键 + * @param string $localKey 当前模型关联键 + */ + public function __construct(Model $parent, $model, $table, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + + if (false !== strpos($table, '\\')) { + $this->pivotName = $table; + $this->middle = basename(str_replace('\\', '/', $table)); + } else { + $this->middle = $table; + } + + $this->query = (new $model)->db(); + $this->pivot = $this->newPivot(); + } + + /** + * 设置中间表模型 + * @access public + * @param $pivot + * @return $this + */ + public function pivot($pivot) + { + $this->pivotName = $pivot; + return $this; + } + + /** + * 设置中间表数据名称 + * @access public + * @param string $name + * @return $this + */ + public function pivotDataName($name) + { + $this->pivotDataName = $name; + return $this; + } + + /** + * 获取中间表更新条件 + * @param $data + * @return array + */ + protected function getUpdateWhere($data) + { + return [ + $this->localKey => $data[$this->localKey], + $this->foreignKey => $data[$this->foreignKey], + ]; + } + + /** + * 实例化中间表模型 + * @access public + * @param array $data + * @param bool $isUpdate + * @return Pivot + * @throws Exception + */ + protected function newPivot($data = [], $isUpdate = false) + { + $class = $this->pivotName ?: '\\think\\model\\Pivot'; + $pivot = new $class($data, $this->parent, $this->middle); + + if ($pivot instanceof Pivot) { + return $isUpdate ? $pivot->isUpdate(true, $this->getUpdateWhere($data)) : $pivot; + } + + throw new Exception('pivot model must extends: \think\model\Pivot'); + } + + /** + * 合成中间表模型 + * @access protected + * @param array|Collection|Paginator $models + */ + protected function hydratePivot($models) + { + foreach ($models as $model) { + $pivot = []; + + foreach ($model->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($model->$key); + } + } + } + + $model->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + } + } + + /** + * 创建关联查询Query对象 + * @access protected + * @return Query + */ + protected function buildQuery() + { + $foreignKey = $this->foreignKey; + $localKey = $this->localKey; + + // 关联查询 + $pk = $this->parent->getPk(); + + $condition[] = ['pivot.' . $localKey, '=', $this->parent->$pk]; + + return $this->belongsToManyQuery($foreignKey, $localKey, $condition); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + $closure($this->query); + } + + $result = $this->buildQuery()->relation($subRelation)->select(); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载select方法 + * @access public + * @param mixed $data + * @return Collection + */ + public function select($data = null) + { + $result = $this->buildQuery()->select($data); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载paginate方法 + * @access public + * @param null $listRows + * @param bool $simple + * @param array $config + * @return Paginator + */ + public function paginate($listRows = null, $simple = false, $config = []) + { + $result = $this->buildQuery()->paginate($listRows, $simple, $config); + $this->hydratePivot($result); + + return $result; + } + + /** + * 重载find方法 + * @access public + * @param mixed $data + * @return Model + */ + public function find($data = null) + { + $result = $this->buildQuery()->find($data); + if ($result) { + $this->hydratePivot([$result]); + } + + return $result; + } + + /** + * 查找多条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return Collection + */ + public function selectOrFail($data = null) + { + return $this->failException(true)->select($data); + } + + /** + * 查找单条记录 如果不存在则抛出异常 + * @access public + * @param array|string|Query|\Closure $data + * @return Model + */ + public function findOrFail($data = null) + { + return $this->failException(true)->find($data); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + * @throws Exception + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 设置中间表的查询条件 + * @access public + * @param string $field + * @param string $op + * @param mixed $condition + * @return $this + */ + public function wherePivot($field, $op = null, $condition = null) + { + $this->query->where('pivot.' . $field, $op, $condition); + return $this; + } + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $pk = $resultSet[0]->getPk(); + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + // 查询关联数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $localKey, 'in', $range], + ], $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询(单个数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + // 查询管理数据 + $data = $this->eagerlyManyToMany([ + ['pivot.' . $this->localKey, '=', $pk], + ], $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + $pk = $result->$pk; + + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + ['pivot.' . $this->localKey, '=', $pk], + ])->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return array + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->belongsToManyQuery($this->foreignKey, $this->localKey, [ + [ + 'pivot.' . $this->localKey, 'exp', $this->query->raw('=' . $this->parent->getTable() . '.' . $this->parent->getPk()), + ], + ])->fetchSql()->$aggregate($field); + } + + /** + * 多对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 + * @return array + */ + protected function eagerlyManyToMany($where, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $closure($this->query); + } + + $list = $this->belongsToManyQuery($this->foreignKey, $this->localKey, $where) + ->with($subRelation) + ->select(); + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $pivot = []; + foreach ($set->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ('pivot' == $name) { + $pivot[$attr] = $val; + unset($set->$key); + } + } + } + + $set->setRelation($this->pivotDataName, $this->newPivot($pivot, true)); + + $data[$pivot[$this->localKey]][] = $set; + } + + return $data; + } + + /** + * BELONGS TO MANY 关联查询 + * @access protected + * @param string $foreignKey 关联模型关联键 + * @param string $localKey 当前模型关联键 + * @param array $condition 关联查询条件 + * @return Query + */ + protected function belongsToManyQuery($foreignKey, $localKey, $condition = []) + { + // 关联查询封装 + $tableName = $this->query->getTable(); + $table = $this->pivot->getTable(); + $fields = $this->getQueryFields($tableName); + + $query = $this->query + ->field($fields) + ->field(true, false, $table, 'pivot', 'pivot__'); + + if (empty($this->baseQuery)) { + $relationFk = $this->query->getPk(); + $query->join([$table => 'pivot'], 'pivot.' . $foreignKey . '=' . $tableName . '.' . $relationFk) + ->where($condition); + } + + return $query; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + */ + public function save($data, array $pivot = []) + { + // 保存关联表/中间表数据 + return $this->attach($data, $pivot); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param array $pivot 中间表额外数据 + * @param bool $samePivot 额外数据是否相同 + * @return array|false + */ + public function saveAll(array $dataSet, array $pivot = [], $samePivot = false) + { + $result = []; + + foreach ($dataSet as $key => $data) { + if (!$samePivot) { + $pivotData = isset($pivot[$key]) ? $pivot[$key] : []; + } else { + $pivotData = $pivot; + } + + $result[] = $this->attach($data, $pivotData); + } + + return empty($result) ? false : $result; + } + + /** + * 附加关联的一个中间表数据 + * @access public + * @param mixed $data 数据 可以使用数组、关联模型对象 或者 关联对象的主键 + * @param array $pivot 中间表额外数据 + * @return array|Pivot + * @throws Exception + */ + public function attach($data, $pivot = []) + { + if (is_array($data)) { + if (key($data) === 0) { + $id = $data; + } else { + // 保存关联表数据 + $model = new $this->model; + $id = $model->insertGetId($data); + } + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + if ($id) { + // 保存中间表数据 + $pk = $this->parent->getPk(); + $pivot[$this->localKey] = $this->parent->$pk; + $ids = (array) $id; + + foreach ($ids as $id) { + $pivot[$this->foreignKey] = $id; + $this->pivot->replace()->save($pivot); + $result[] = $this->newPivot($pivot, true); + } + + if (count($result) == 1) { + // 返回中间表模型对象 + $result = $result[0]; + } + + return $result; + } else { + throw new Exception('miss relation data'); + } + } + + /** + * 判断是否存在关联数据 + * @access public + * @param mixed $data 数据 可以使用关联模型对象 或者 关联对象的主键 + * @return Pivot + * @throws Exception + */ + public function attached($data) + { + if ($data instanceof Model) { + $id = $data->getKey(); + } else { + $id = $data; + } + + $pivot = $this->pivot + ->where($this->localKey, $this->parent->getKey()) + ->where($this->foreignKey, $id) + ->find(); + + return $pivot ?: false; + } + + /** + * 解除关联的一个中间表数据 + * @access public + * @param integer|array $data 数据 可以使用关联对象的主键 + * @param bool $relationDel 是否同时删除关联表数据 + * @return integer + */ + public function detach($data = null, $relationDel = false) + { + if (is_array($data)) { + $id = $data; + } elseif (is_numeric($data) || is_string($data)) { + // 根据关联表主键直接写入中间表 + $id = $data; + } elseif ($data instanceof Model) { + // 根据关联表主键直接写入中间表 + $relationFk = $data->getPk(); + $id = $data->$relationFk; + } + + // 删除中间表数据 + $pk = $this->parent->getPk(); + $pivot[] = [$this->localKey, '=', $this->parent->$pk]; + + if (isset($id)) { + $pivot[] = [$this->foreignKey, is_array($id) ? 'in' : '=', $id]; + } + + $result = $this->pivot->where($pivot)->delete(); + + // 删除关联表数据 + if (isset($id) && $relationDel) { + $model = $this->model; + $model::destroy($id); + } + + return $result; + } + + /** + * 数据同步 + * @access public + * @param array $ids + * @param bool $detaching + * @return array + */ + public function sync($ids, $detaching = true) + { + $changes = [ + 'attached' => [], + 'detached' => [], + 'updated' => [], + ]; + + $pk = $this->parent->getPk(); + + $current = $this->pivot + ->where($this->localKey, $this->parent->$pk) + ->column($this->foreignKey); + + $records = []; + + foreach ($ids as $key => $value) { + if (!is_array($value)) { + $records[$value] = []; + } else { + $records[$key] = $value; + } + } + + $detach = array_diff($current, array_keys($records)); + + if ($detaching && count($detach) > 0) { + $this->detach($detach); + $changes['detached'] = $detach; + } + + foreach ($records as $id => $attributes) { + if (!in_array($id, $current)) { + $this->attach($id, $attributes); + $changes['attached'][] = $id; + } elseif (count($attributes) > 0 && $this->attach($id, $attributes)) { + $changes['updated'][] = $id; + } + } + + return $changes; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + $table = $this->pivot->getTable(); + + $this->query + ->join([$table => 'pivot'], 'pivot.' . $this->foreignKey . '=' . $this->query->getTable() . '.' . $this->query->getPk()) + ->where('pivot.' . $this->localKey, $this->parent->$pk); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasMany.php b/thinkphp/library/think/model/relation/HasMany.php new file mode 100644 index 0000000..72d8314 --- /dev/null +++ b/thinkphp/library/think/model/relation/HasMany.php @@ -0,0 +1,351 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasMany extends Relation +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + $closure($this->query); + } + + $list = $this->query + ->where($this->foreignKey, $this->parent->{$this->localKey}) + ->relation($subRelation) + ->select(); + + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $where = [ + [$this->foreignKey, 'in', $range], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + $pk = $result->$localKey; + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + + if (isset($result->$localKey)) { + $pk = $result->$localKey; + $where = [ + [$this->foreignKey, '=', $pk], + ]; + $data = $this->eagerlyOneToMany($where, $relation, $subRelation, $closure); + + // 关联数据封装 + if (!isset($data[$pk])) { + $data[$pk] = []; + } + + foreach ($data[$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$pk])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 一对多 关联模型预查询 + * @access public + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure + * @return array + */ + protected function eagerlyOneToMany($where, $relation, $subRelation = '', $closure = null) + { + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($this->foreignKey); + + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $closure($this->query); + } + + $list = $this->query->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$foreignKey][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @param boolean $replace 是否自动识别更新和写入 + * @return Model|false + */ + public function save($data, $replace = true) + { + $model = $this->make($data); + + return $model->replace($replace)->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @param boolean $replace 是否自动识别更新和写入 + * @return array|false + */ + public function saveAll(array $dataSet, $replace = true) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data, $replace); + } + + return empty($result) ? false : $result; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + return $this->parent->db() + ->alias($model) + ->field($model . '.*') + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $joinType) + ->group($relation . '.' . $this->foreignKey) + ->having('count(' . $id . ')' . $operator . $count); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db() + ->alias($model) + ->group($model . '.' . $this->localKey) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey) + ->where($where); + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasManyThrough.php b/thinkphp/library/think/model/relation/HasManyThrough.php new file mode 100644 index 0000000..7c7acaa --- /dev/null +++ b/thinkphp/library/think/model/relation/HasManyThrough.php @@ -0,0 +1,156 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class HasManyThrough extends Relation +{ + // 中间关联表外键 + protected $throughKey; + // 中间表模型 + protected $through; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $through 中间模型名 + * @param string $foreignKey 关联外键 + * @param string $throughKey 关联外键 + * @param string $localKey 当前主键 + */ + public function __construct(Model $parent, $model, $through, $foreignKey, $throughKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->through = $through; + $this->foreignKey = $foreignKey; + $this->throughKey = $throughKey; + $this->localKey = $localKey; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + $closure($this->query); + } + + $this->baseQuery(); + + return $this->query->relation($subRelation)->select(); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + {} + + /** + * 预载入关联查询 返回模型对象 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + {} + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + {} + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $through = $this->through; + $alias = Loader::parseName(basename(str_replace('\\', '/', $this->model))); + $throughTable = $through::getTable(); + $pk = (new $through)->getPk(); + $throughKey = $this->throughKey; + $modelTable = $this->parent->getTable(); + $fields = $this->getQueryFields($alias); + + $this->query + ->field($fields) + ->alias($alias) + ->join($throughTable, $throughTable . '.' . $pk . '=' . $alias . '.' . $throughKey) + ->join($modelTable, $modelTable . '.' . $this->localKey . '=' . $throughTable . '.' . $this->foreignKey) + ->where($throughTable . '.' . $this->foreignKey, $this->parent->{$this->localKey}); + + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/HasOne.php b/thinkphp/library/think/model/relation/HasOne.php new file mode 100644 index 0000000..d8e3ec7 --- /dev/null +++ b/thinkphp/library/think/model/relation/HasOne.php @@ -0,0 +1,285 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Loader; +use think\Model; + +class HasOne extends OneToOne +{ + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $foreignKey 关联外键 + * @param string $localKey 当前模型主键 + */ + public function __construct(Model $parent, $model, $foreignKey, $localKey) + { + $this->parent = $parent; + $this->model = $model; + $this->foreignKey = $foreignKey; + $this->localKey = $localKey; + $this->joinType = 'INNER'; + $this->query = (new $model)->db(); + + if (get_class($parent) == $model) { + $this->selfRelation = true; + } + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $localKey = $this->localKey; + + if ($closure) { + $closure($this->query); + } + + // 判断关联类型执行查询 + $relationModel = $this->query + ->removeWhereField($this->foreignKey) + ->where($this->foreignKey, $this->parent->$localKey) + ->relation($subRelation) + ->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 创建关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->foreignKey, '=' . $this->parent->getTable() . '.' . $this->localKey) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $localKey = $this->localKey; + + if (!isset($result->$localKey)) { + return 0; + } + + if ($closure) { + $return = $closure($this->query); + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where($this->foreignKey, '=', $result->$localKey) + ->$aggregate($field); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + return $this->parent->db() + ->alias($model) + ->whereExists(function ($query) use ($table, $model, $relation, $localKey, $foreignKey) { + $query->table([$table => $relation]) + ->field($relation . '.' . $foreignKey) + ->whereExp($model . '.' . $localKey, '=' . $relation . '.' . $foreignKey); + }); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + $table = $this->query->getTable(); + $model = basename(str_replace('\\', '/', get_class($this->parent))); + $relation = basename(str_replace('\\', '/', $this->model)); + + if (is_array($where)) { + $this->getQueryWhere($where, $relation); + } + + $fields = $this->getRelationQueryFields($fields, $model); + + return $this->parent->db() + ->alias($model) + ->field($fields) + ->join([$table => $relation], $model . '.' . $this->localKey . '=' . $relation . '.' . $this->foreignKey, $this->joinType) + ->where($where); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $range = []; + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (isset($result->$localKey)) { + $range[] = $result->$localKey; + } + } + + if (!empty($range)) { + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, 'in', $range], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + // 设置关联属性 + $result->setRelation($attr, $relationModel); + } + } + } + } + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + protected function eagerlyOne(&$result, $relation, $subRelation, $closure) + { + $localKey = $this->localKey; + $foreignKey = $this->foreignKey; + + $this->query->removeWhereField($foreignKey); + + $data = $this->eagerlyWhere([ + [$foreignKey, '=', $result->$localKey], + ], $foreignKey, $relation, $subRelation, $closure); + + // 关联模型 + if (!isset($data[$result->$localKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$localKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + // 绑定关联属性 + $this->bindAttr($relationModel, $result, $this->bindAttr); + } else { + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery)) { + if (isset($this->parent->{$this->localKey})) { + // 关联查询带入关联条件 + $this->query->where($this->foreignKey, '=', $this->parent->{$this->localKey}); + } + + $this->baseQuery = true; + } + } +} diff --git a/thinkphp/library/think/model/relation/MorphMany.php b/thinkphp/library/think/model/relation/MorphMany.php new file mode 100644 index 0000000..1a7f15e --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphMany.php @@ -0,0 +1,341 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphMany extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return \think\Collection + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + $closure($this->query); + } + + $this->baseQuery(); + + $list = $this->query->relation($subRelation)->select(); + $parent = clone $this->parent; + + foreach ($list as &$model) { + $model->setParent($parent); + } + + return $list; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + throw new Exception('relation not support: has'); + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $where = [ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $data[$result->$pk] = []; + } + + foreach ($data[$result->$pk] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $this->resultSetBuild($data[$result->$pk])); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $key = $result->$pk; + $where = [ + [$this->morphKey, '=', $key], + [$this->morphType, '=', $this->type], + ]; + $data = $this->eagerlyMorphToMany($where, $relation, $subRelation, $closure); + + if (!isset($data[$key])) { + $data[$key] = []; + } + + foreach ($data[$key] as &$relationModel) { + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $this->resultSetBuild($data[$key])); + } + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + { + $pk = $result->getPk(); + + if (!isset($result->$pk)) { + return 0; + } + + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $name = $return; + } + } + + return $this->query + ->where([ + [$this->morphKey, '=', $result->$pk], + [$this->morphType, '=', $this->type], + ]) + ->$aggregate($field); + } + + /** + * 获取关联统计子查询 + * @access public + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $aggregateAlias 聚合字段别名 + * @return string + */ + public function getRelationCountQuery($closure, $aggregate = 'count', $field = '*', &$aggregateAlias = '') + { + if ($closure) { + $return = $closure($this->query); + + if ($return && is_string($return)) { + $aggregateAlias = $return; + } + } + + return $this->query + ->whereExp($this->morphKey, '=' . $this->parent->getTable() . '.' . $this->parent->getPk()) + ->where($this->morphType, '=', $this->type) + ->fetchSql() + ->$aggregate($field); + } + + /** + * 多态一对多 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToMany($where, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + $this->query->removeOption('where'); + + if ($closure) { + $closure($this->query); + } + + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + foreach ($list as $set) { + $data[$set->$morphKey][] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 + * @return Model|false + */ + public function save($data) + { + $model = $this->make($data); + + return $model->save($data) ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 批量保存当前关联数据对象 + * @access public + * @param array $dataSet 数据集 + * @return array|false + */ + public function saveAll(array $dataSet) + { + $result = []; + + foreach ($dataSet as $key => $data) { + $result[] = $this->save($data); + } + + return empty($result) ? false : $result; + } + + /** + * 执行基础查询(仅执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/MorphOne.php b/thinkphp/library/think/model/relation/MorphOne.php new file mode 100644 index 0000000..716539f --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphOne.php @@ -0,0 +1,256 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphOne extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态类型 + protected $type; + + /** + * 构造函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $model 模型名 + * @param string $morphKey 关联外键 + * @param string $morphType 多态字段名 + * @param string $type 多态类型 + */ + public function __construct(Model $parent, $model, $morphKey, $morphType, $type) + { + $this->parent = $parent; + $this->model = $model; + $this->type = $type; + $this->morphKey = $morphKey; + $this->morphType = $morphType; + $this->query = (new $model)->db(); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + if ($closure) { + $closure($this->query); + } + + $this->baseQuery(); + + $relationModel = $this->query->relation($subRelation)->find(); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphType = $this->morphType; + $morphKey = $this->morphKey; + $type = $this->type; + $range = []; + + foreach ($resultSet as $result) { + $pk = $result->getPk(); + // 获取关联外键列表 + if (isset($result->$pk)) { + $range[] = $result->$pk; + } + } + + if (!empty($range)) { + $data = $this->eagerlyMorphToOne([ + [$morphKey, 'in', $range], + [$morphType, '=', $type], + ], $relation, $subRelation, $closure); + + // 关联属性名 + $attr = Loader::parseName($relation); + + // 关联数据封装 + foreach ($resultSet as $result) { + if (!isset($data[$result->$pk])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $pk = $result->getPk(); + + if (isset($result->$pk)) { + $pk = $result->$pk; + $data = $this->eagerlyMorphToOne([ + [$this->morphKey, '=', $pk], + [$this->morphType, '=', $this->type], + ], $relation, $subRelation, $closure); + + if (isset($data[$pk])) { + $relationModel = $data[$pk]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + } + + /** + * 多态一对一 关联模型预查询 + * @access protected + * @param array $where 关联预查询条件 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure 闭包 + * @return array + */ + protected function eagerlyMorphToOne($where, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $closure($this->query); + } + + $list = $this->query->where($where)->with($subRelation)->select(); + $morphKey = $this->morphKey; + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$morphKey] = $set; + } + + return $data; + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 + * @return Model|false + */ + public function save($data) + { + $model = $this->make($data); + return $model->save() ? $model : false; + } + + /** + * 创建关联对象实例 + * @param array $data + * @return Model + */ + public function make($data = []) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + // 保存关联表数据 + $pk = $this->parent->getPk(); + + $data[$this->morphKey] = $this->parent->$pk; + $data[$this->morphType] = $this->type; + + return new $this->model($data); + } + + /** + * 执行基础查询(进执行一次) + * @access protected + * @return void + */ + protected function baseQuery() + { + if (empty($this->baseQuery) && $this->parent->getData()) { + $pk = $this->parent->getPk(); + + $this->query->where([ + [$this->morphKey, '=', $this->parent->$pk], + [$this->morphType, '=', $this->type], + ]); + $this->baseQuery = true; + } + } + +} diff --git a/thinkphp/library/think/model/relation/MorphTo.php b/thinkphp/library/think/model/relation/MorphTo.php new file mode 100644 index 0000000..6da6a02 --- /dev/null +++ b/thinkphp/library/think/model/relation/MorphTo.php @@ -0,0 +1,307 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +class MorphTo extends Relation +{ + // 多态字段 + protected $morphKey; + protected $morphType; + // 多态别名 + protected $alias; + // 关联名 + protected $relation; + + /** + * 架构函数 + * @access public + * @param Model $parent 上级模型对象 + * @param string $morphType 多态字段名 + * @param string $morphKey 外键名 + * @param array $alias 多态别名定义 + * @param string $relation 关联名 + */ + public function __construct(Model $parent, $morphType, $morphKey, $alias = [], $relation = null) + { + $this->parent = $parent; + $this->morphType = $morphType; + $this->morphKey = $morphKey; + $this->alias = $alias; + $this->relation = $relation; + } + + /** + * 获取当前的关联模型类的实例 + * @access public + * @return Model + */ + public function getModel() + { + $morphType = $this->morphType; + $model = $this->parseModel($this->parent->$morphType); + + return (new $model); + } + + /** + * 延迟获取关联数据 + * @access public + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包查询条件 + * @return Model + */ + public function getRelation($subRelation = '', $closure = null) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + // 多态模型 + $model = $this->parseModel($this->parent->$morphType); + + // 主键数据 + $pk = $this->parent->$morphKey; + + $relationModel = (new $model)->relation($subRelation)->find($pk); + + if ($relationModel) { + $relationModel->setParent(clone $this->parent); + } + + return $relationModel; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param string $operator 比较操作符 + * @param integer $count 个数 + * @param string $id 关联表的统计字段 + * @param string $joinType JOIN类型 + * @return Query + */ + public function has($operator = '>=', $count = 1, $id = '*', $joinType = 'INNER') + { + return $this->parent; + } + + /** + * 根据关联条件查询当前模型 + * @access public + * @param mixed $where 查询条件(数组或者闭包) + * @param mixed $fields 字段 + * @return Query + */ + public function hasWhere($where = [], $fields = null) + { + throw new Exception('relation not support: hasWhere'); + } + + /** + * 解析模型的完整命名空间 + * @access protected + * @param string $model 模型名(或者完整类名) + * @return string + */ + protected function parseModel($model) + { + if (isset($this->alias[$model])) { + $model = $this->alias[$model]; + } + + if (false === strpos($model, '\\')) { + $path = explode('\\', get_class($this->parent)); + array_pop($path); + array_push($path, Loader::parseName($model, 1)); + $model = implode('\\', $path); + } + + return $model; + } + + /** + * 设置多态别名 + * @access public + * @param array $alias 别名定义 + * @return $this + */ + public function setAlias($alias) + { + $this->alias = $alias; + + return $this; + } + + /** + * 移除关联查询参数 + * @access public + * @return $this + */ + public function removeOption() + { + return $this; + } + + /** + * 预载入关联查询 + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + * @throws Exception + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $range = []; + + foreach ($resultSet as $result) { + // 获取关联外键列表 + if (!empty($result->$morphKey)) { + $range[$result->$morphType][] = $result->$morphKey; + } + } + + if (!empty($range)) { + // 关联属性名 + $attr = Loader::parseName($relation); + + foreach ($range as $key => $val) { + // 多态类型映射 + $model = $this->parseModel($key); + $obj = new $model; + $pk = $obj->getPk(); + $list = $obj->all($val, $subRelation); + $data = []; + + foreach ($list as $k => $vo) { + $data[$vo->$pk] = $vo; + } + + foreach ($resultSet as $result) { + if ($key == $result->$morphType) { + // 关联模型 + if (!isset($data[$result->$morphKey])) { + $relationModel = null; + } else { + $relationModel = $data[$result->$morphKey]; + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + $result->setRelation($attr, $relationModel); + } + } + } + } + } + + /** + * 预载入关联查询 + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure) + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + // 多态类型映射 + $model = $this->parseModel($result->{$this->morphType}); + + $this->eagerlyMorphToOne($model, $relation, $result, $subRelation); + } + + /** + * 关联统计 + * @access public + * @param Model $result 数据对象 + * @param \Closure $closure 闭包 + * @param string $aggregate 聚合查询方法 + * @param string $field 字段 + * @param string $name 统计字段别名 + * @return integer + */ + public function relationCount($result, $closure, $aggregate = 'count', $field = '*', &$name = '') + {} + + /** + * 多态MorphTo 关联模型预查询 + * @access protected + * @param string $model 关联模型对象 + * @param string $relation 关联名 + * @param Model $result + * @param string $subRelation 子关联 + * @return void + */ + protected function eagerlyMorphToOne($model, $relation, &$result, $subRelation = '') + { + // 预载入关联查询 支持嵌套预载入 + $pk = $this->parent->{$this->morphKey}; + $data = (new $model)->with($subRelation)->find($pk); + + if ($data) { + $data->setParent(clone $result); + $data->isUpdate(true); + } + + $result->setRelation(Loader::parseName($relation), $data ?: null); + } + + /** + * 添加关联数据 + * @access public + * @param Model $model 关联模型对象 + * @param string $type 多态类型 + * @return Model + */ + public function associate($model, $type = '') + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + $pk = $model->getPk(); + + $this->parent->setAttr($morphKey, $model->$pk); + $this->parent->setAttr($morphType, $type ?: get_class($model)); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, $model); + } + + /** + * 注销关联数据 + * @access public + * @return Model + */ + public function dissociate() + { + $morphKey = $this->morphKey; + $morphType = $this->morphType; + + $this->parent->setAttr($morphKey, null); + $this->parent->setAttr($morphType, null); + $this->parent->save(); + + return $this->parent->setRelation($this->relation, null); + } + +} diff --git a/thinkphp/library/think/model/relation/OneToOne.php b/thinkphp/library/think/model/relation/OneToOne.php new file mode 100644 index 0000000..59fce77 --- /dev/null +++ b/thinkphp/library/think/model/relation/OneToOne.php @@ -0,0 +1,334 @@ + +// +---------------------------------------------------------------------- + +namespace think\model\relation; + +use think\db\Query; +use think\Exception; +use think\Loader; +use think\Model; +use think\model\Relation; + +/** + * Class OneToOne + * @package think\model\relation + * + */ +abstract class OneToOne extends Relation +{ + // 预载入方式 0 -JOIN 1 -IN + protected $eagerlyType = 1; + // 当前关联的JOIN类型 + protected $joinType; + // 要绑定的属性 + protected $bindAttr = []; + // 关联名 + protected $relation; + + /** + * 设置join类型 + * @access public + * @param string $type JOIN类型 + * @return $this + */ + public function joinType($type) + { + $this->joinType = $type; + return $this; + } + + /** + * 预载入关联查询(JOIN方式) + * @access public + * @param Query $query 查询对象 + * @param string $relation 关联名 + * @param mixed $field 关联字段 + * @param string $joinType JOIN方式 + * @param \Closure $closure 闭包条件 + * @param bool $first + * @return void + */ + public function eagerly(Query $query, $relation, $field, $joinType, $closure, $first) + { + $name = Loader::parseName(basename(str_replace('\\', '/', get_class($this->parent)))); + + if ($first) { + $table = $query->getTable(); + $query->table([$table => $name]); + + if ($query->getOptions('field')) { + $masterField = $query->getOptions('field'); + $query->removeOption('field'); + } else { + $masterField = true; + } + + $query->field($masterField, false, $table, $name); + } + + // 预载入封装 + $joinTable = $this->query->getTable(); + $joinAlias = $relation; + $joinType = $joinType ?: $this->joinType; + + $query->via($joinAlias); + + if ($this instanceof BelongsTo) { + $joinOn = $name . '.' . $this->foreignKey . '=' . $joinAlias . '.' . $this->localKey; + } else { + $joinOn = $name . '.' . $this->localKey . '=' . $joinAlias . '.' . $this->foreignKey; + } + + if ($closure) { + // 执行闭包查询 + $closure($query); + // 使用withField指定获取关联的字段,如 + // $query->where(['id'=>1])->withField('id,name'); + if ($query->getOptions('with_field')) { + $field = $query->getOptions('with_field'); + $query->removeOption('with_field'); + } + } + + $query->join([$joinTable => $joinAlias], $joinOn, $joinType) + ->field($field, false, $joinTable, $joinAlias, $relation . '__'); + } + + /** + * 预载入关联查询(数据集) + * @access protected + * @param array $resultSet + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlySet(&$resultSet, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据) + * @access protected + * @param Model $result + * @param string $relation + * @param string $subRelation + * @param \Closure $closure + * @return mixed + */ + abstract protected function eagerlyOne(&$result, $relation, $subRelation, $closure); + + /** + * 预载入关联查询(数据集) + * @access public + * @param array $resultSet 数据集 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResultSet(&$resultSet, $relation, $subRelation, $closure, $join = false) + { + if ($join || 0 == $this->eagerlyType) { + // 模型JOIN关联组装 + foreach ($resultSet as $result) { + $this->match($this->model, $relation, $result); + } + } else { + // IN查询 + $this->eagerlySet($resultSet, $relation, $subRelation, $closure); + } + } + + /** + * 预载入关联查询(数据) + * @access public + * @param Model $result 数据对象 + * @param string $relation 当前关联名 + * @param string $subRelation 子关联名 + * @param \Closure $closure 闭包 + * @param bool $join 是否为JOIN方式 + * @return void + */ + public function eagerlyResult(&$result, $relation, $subRelation, $closure, $join = false) + { + if (0 == $this->eagerlyType || $join) { + // 模型JOIN关联组装 + $this->match($this->model, $relation, $result); + } else { + // IN查询 + $this->eagerlyOne($result, $relation, $subRelation, $closure); + } + } + + /** + * 保存(新增)当前关联数据对象 + * @access public + * @param mixed $data 数据 可以使用数组 关联模型对象 和 关联对象的主键 + * @return Model|false + */ + public function save($data) + { + if ($data instanceof Model) { + $data = $data->getData(); + } + + $model = new $this->model; + // 保存关联表数据 + $data[$this->foreignKey] = $this->parent->{$this->localKey}; + + return $model->save($data) ? $model : false; + } + + /** + * 设置预载入方式 + * @access public + * @param integer $type 预载入方式 0 JOIN查询 1 IN查询 + * @return $this + */ + public function setEagerlyType($type) + { + $this->eagerlyType = $type; + + return $this; + } + + /** + * 获取预载入方式 + * @access public + * @return integer + */ + public function getEagerlyType() + { + return $this->eagerlyType; + } + + /** + * 绑定关联表的属性到父模型属性 + * @access public + * @param mixed $attr 要绑定的属性列表 + * @return $this + */ + public function bind($attr) + { + if (is_string($attr)) { + $attr = explode(',', $attr); + } + $this->bindAttr = $attr; + + return $this; + } + + /** + * 获取绑定属性 + * @access public + * @return array + */ + public function getBindAttr() + { + return $this->bindAttr; + } + + /** + * 一对一 关联模型预查询拼装 + * @access public + * @param string $model 模型名称 + * @param string $relation 关联名 + * @param Model $result 模型对象实例 + * @return void + */ + protected function match($model, $relation, &$result) + { + // 重新组装模型数据 + foreach ($result->getData() as $key => $val) { + if (strpos($key, '__')) { + list($name, $attr) = explode('__', $key, 2); + if ($name == $relation) { + $list[$name][$attr] = $val; + unset($result->$key); + } + } + } + + if (isset($list[$relation])) { + $array = array_unique($list[$relation]); + + if (count($array) == 1 && null === current($array)) { + $relationModel = null; + } else { + $relationModel = new $model($list[$relation]); + $relationModel->setParent(clone $result); + $relationModel->isUpdate(true); + } + + if (!empty($this->bindAttr)) { + $this->bindAttr($relationModel, $result, $this->bindAttr); + } + } else { + $relationModel = null; + } + + $result->setRelation(Loader::parseName($relation), $relationModel); + } + + /** + * 绑定关联属性到父模型 + * @access protected + * @param Model $model 关联模型对象 + * @param Model $result 父模型对象 + * @return void + * @throws Exception + */ + protected function bindAttr($model, &$result) + { + foreach ($this->bindAttr as $key => $attr) { + $key = is_numeric($key) ? $attr : $key; + if (isset($result->$key)) { + throw new Exception('bind attr has exists:' . $key); + } else { + $result->setAttr($key, $model ? $model->$attr : null); + } + } + } + + /** + * 一对一 关联模型预查询(IN方式) + * @access public + * @param array $where 关联预查询条件 + * @param string $key 关联键名 + * @param string $relation 关联名 + * @param string $subRelation 子关联 + * @param \Closure $closure + * @return array + */ + protected function eagerlyWhere($where, $key, $relation, $subRelation = '', $closure = null) + { + // 预载入关联查询 支持嵌套预载入 + if ($closure) { + $closure($this->query); + + if ($field = $this->query->getOptions('with_field')) { + $this->query->field($field)->removeOption('with_field'); + } + } + + $list = $this->query->where($where)->with($subRelation)->select(); + + // 组装模型数据 + $data = []; + + foreach ($list as $set) { + $data[$set->$key] = $set; + } + + return $data; + } + +} diff --git a/thinkphp/library/think/paginator/driver/Bootstrap.php b/thinkphp/library/think/paginator/driver/Bootstrap.php new file mode 100644 index 0000000..ab5315c --- /dev/null +++ b/thinkphp/library/think/paginator/driver/Bootstrap.php @@ -0,0 +1,206 @@ + +// +---------------------------------------------------------------------- + +namespace think\paginator\driver; + +use think\Paginator; + +class Bootstrap extends Paginator +{ + + /** + * 上一页按钮 + * @param string $text + * @return string + */ + protected function getPreviousButton($text = "«") + { + + if ($this->currentPage() <= 1) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url( + $this->currentPage() - 1 + ); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 下一页按钮 + * @param string $text + * @return string + */ + protected function getNextButton($text = '»') + { + if (!$this->hasMore) { + return $this->getDisabledTextWrapper($text); + } + + $url = $this->url($this->currentPage() + 1); + + return $this->getPageLinkWrapper($url, $text); + } + + /** + * 页码按钮 + * @return string + */ + protected function getLinks() + { + if ($this->simple) { + return ''; + } + + $block = [ + 'first' => null, + 'slider' => null, + 'last' => null, + ]; + + $side = 3; + $window = $side * 2; + + if ($this->lastPage < $window + 6) { + $block['first'] = $this->getUrlRange(1, $this->lastPage); + } elseif ($this->currentPage <= $window) { + $block['first'] = $this->getUrlRange(1, $window + 2); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } elseif ($this->currentPage > ($this->lastPage - $window)) { + $block['first'] = $this->getUrlRange(1, 2); + $block['last'] = $this->getUrlRange($this->lastPage - ($window + 2), $this->lastPage); + } else { + $block['first'] = $this->getUrlRange(1, 2); + $block['slider'] = $this->getUrlRange($this->currentPage - $side, $this->currentPage + $side); + $block['last'] = $this->getUrlRange($this->lastPage - 1, $this->lastPage); + } + + $html = ''; + + if (is_array($block['first'])) { + $html .= $this->getUrlLinks($block['first']); + } + + if (is_array($block['slider'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['slider']); + } + + if (is_array($block['last'])) { + $html .= $this->getDots(); + $html .= $this->getUrlLinks($block['last']); + } + + return $html; + } + + /** + * 渲染分页html + * @return mixed + */ + public function render() + { + if ($this->hasPages()) { + if ($this->simple) { + return sprintf( + '
    %s %s
', + $this->getPreviousButton(), + $this->getNextButton() + ); + } else { + return sprintf( + '
    %s %s %s
', + $this->getPreviousButton(), + $this->getLinks(), + $this->getNextButton() + ); + } + } + } + + /** + * 生成一个可点击的按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getAvailablePageWrapper($url, $page) + { + return '
  • ' . $page . '
  • '; + } + + /** + * 生成一个禁用的按钮 + * + * @param string $text + * @return string + */ + protected function getDisabledTextWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成一个激活的按钮 + * + * @param string $text + * @return string + */ + protected function getActivePageWrapper($text) + { + return '
  • ' . $text . '
  • '; + } + + /** + * 生成省略号按钮 + * + * @return string + */ + protected function getDots() + { + return $this->getDisabledTextWrapper('...'); + } + + /** + * 批量生成页码按钮. + * + * @param array $urls + * @return string + */ + protected function getUrlLinks(array $urls) + { + $html = ''; + + foreach ($urls as $page => $url) { + $html .= $this->getPageLinkWrapper($url, $page); + } + + return $html; + } + + /** + * 生成普通页码按钮 + * + * @param string $url + * @param int $page + * @return string + */ + protected function getPageLinkWrapper($url, $page) + { + if ($this->currentPage() == $page) { + return $this->getActivePageWrapper($page); + } + + return $this->getAvailablePageWrapper($url, $page); + } +} diff --git a/thinkphp/library/think/process/Builder.php b/thinkphp/library/think/process/Builder.php new file mode 100644 index 0000000..da56163 --- /dev/null +++ b/thinkphp/library/think/process/Builder.php @@ -0,0 +1,233 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +use think\Process; + +class Builder +{ + private $arguments; + private $cwd; + private $env = null; + private $input; + private $timeout = 60; + private $options = []; + private $inheritEnv = true; + private $prefix = []; + private $outputDisabled = false; + + /** + * 构造方法 + * @param string[] $arguments 参数 + */ + public function __construct(array $arguments = []) + { + $this->arguments = $arguments; + } + + /** + * 创建一个实例 + * @param string[] $arguments 参数 + * @return self + */ + public static function create(array $arguments = []) + { + return new static($arguments); + } + + /** + * 添加一个参数 + * @param string $argument 参数 + * @return self + */ + public function add($argument) + { + $this->arguments[] = $argument; + + return $this; + } + + /** + * 添加一个前缀 + * @param string|array $prefix + * @return self + */ + public function setPrefix($prefix) + { + $this->prefix = is_array($prefix) ? $prefix : [$prefix]; + + return $this; + } + + /** + * 设置参数 + * @param string[] $arguments + * @return self + */ + public function setArguments(array $arguments) + { + $this->arguments = $arguments; + + return $this; + } + + /** + * 设置工作目录 + * @param null|string $cwd + * @return self + */ + public function setWorkingDirectory($cwd) + { + $this->cwd = $cwd; + + return $this; + } + + /** + * 是否初始化环境变量 + * @param bool $inheritEnv + * @return self + */ + public function inheritEnvironmentVariables($inheritEnv = true) + { + $this->inheritEnv = $inheritEnv; + + return $this; + } + + /** + * 设置环境变量 + * @param string $name + * @param null|string $value + * @return self + */ + public function setEnv($name, $value) + { + $this->env[$name] = $value; + + return $this; + } + + /** + * 添加环境变量 + * @param array $variables + * @return self + */ + public function addEnvironmentVariables(array $variables) + { + $this->env = array_replace($this->env, $variables); + + return $this; + } + + /** + * 设置输入 + * @param mixed $input + * @return self + */ + public function setInput($input) + { + $this->input = Utils::validateInput(sprintf('%s::%s', __CLASS__, __FUNCTION__), $input); + + return $this; + } + + /** + * 设置超时时间 + * @param float|null $timeout + * @return self + */ + public function setTimeout($timeout) + { + if (null === $timeout) { + $this->timeout = null; + + return $this; + } + + $timeout = (float) $timeout; + + if ($timeout < 0) { + throw new \InvalidArgumentException('The timeout value must be a valid positive integer or float number.'); + } + + $this->timeout = $timeout; + + return $this; + } + + /** + * 设置proc_open选项 + * @param string $name + * @param string $value + * @return self + */ + public function setOption($name, $value) + { + $this->options[$name] = $value; + + return $this; + } + + /** + * 禁止输出 + * @return self + */ + public function disableOutput() + { + $this->outputDisabled = true; + + return $this; + } + + /** + * 开启输出 + * @return self + */ + public function enableOutput() + { + $this->outputDisabled = false; + + return $this; + } + + /** + * 创建一个Process实例 + * @return Process + */ + public function getProcess() + { + if (0 === count($this->prefix) && 0 === count($this->arguments)) { + throw new \LogicException('You must add() command arguments before calling getProcess().'); + } + + $options = $this->options; + + $arguments = array_merge($this->prefix, $this->arguments); + $script = implode(' ', array_map([__NAMESPACE__ . '\\Utils', 'escapeArgument'], $arguments)); + + if ($this->inheritEnv) { + // include $_ENV for BC purposes + $env = array_replace($_ENV, $_SERVER, $this->env); + } else { + $env = $this->env; + } + + $process = new Process($script, $this->cwd, $env, $this->input, $this->timeout, $options); + + if ($this->outputDisabled) { + $process->disableOutput(); + } + + return $process; + } +} diff --git a/thinkphp/library/think/process/Utils.php b/thinkphp/library/think/process/Utils.php new file mode 100644 index 0000000..f94c648 --- /dev/null +++ b/thinkphp/library/think/process/Utils.php @@ -0,0 +1,75 @@ + +// +---------------------------------------------------------------------- + +namespace think\process; + +class Utils +{ + + /** + * 转义字符串 + * @param string $argument + * @return string + */ + public static function escapeArgument($argument) + { + + if ('' === $argument) { + return escapeshellarg($argument); + } + $escapedArgument = ''; + $quote = false; + foreach (preg_split('/(")/i', $argument, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE) as $part) { + if ('"' === $part) { + $escapedArgument .= '\\"'; + } elseif (self::isSurroundedBy($part, '%')) { + // Avoid environment variable expansion + $escapedArgument .= '^%"' . substr($part, 1, -1) . '"^%'; + } else { + // escape trailing backslash + if ('\\' === substr($part, -1)) { + $part .= '\\'; + } + $quote = true; + $escapedArgument .= $part; + } + } + if ($quote) { + $escapedArgument = '"' . $escapedArgument . '"'; + } + return $escapedArgument; + } + + /** + * 验证并进行规范化Process输入。 + * @param string $caller + * @param mixed $input + * @return string + * @throws \InvalidArgumentException + */ + public static function validateInput($caller, $input) + { + if (null !== $input) { + if (is_resource($input)) { + return $input; + } + if (is_scalar($input)) { + return (string) $input; + } + throw new \InvalidArgumentException(sprintf('%s only accepts strings or stream resources.', $caller)); + } + return $input; + } + + private static function isSurroundedBy($arg, $char) + { + return 2 < strlen($arg) && $char === $arg[0] && $char === $arg[strlen($arg) - 1]; + } + +} diff --git a/thinkphp/library/think/process/exception/Faild.php b/thinkphp/library/think/process/exception/Faild.php new file mode 100644 index 0000000..38647bc --- /dev/null +++ b/thinkphp/library/think/process/exception/Faild.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Faild extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/thinkphp/library/think/process/exception/Failed.php b/thinkphp/library/think/process/exception/Failed.php new file mode 100644 index 0000000..5295082 --- /dev/null +++ b/thinkphp/library/think/process/exception/Failed.php @@ -0,0 +1,42 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Failed extends \RuntimeException +{ + + private $process; + + public function __construct(Process $process) + { + if ($process->isSuccessful()) { + throw new \InvalidArgumentException('Expected a failed process, but the given process was successful.'); + } + + $error = sprintf('The command "%s" failed.' . "\nExit Code: %s(%s)", $process->getCommandLine(), $process->getExitCode(), $process->getExitCodeText()); + + if (!$process->isOutputDisabled()) { + $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s", $process->getOutput(), $process->getErrorOutput()); + } + + parent::__construct($error); + + $this->process = $process; + } + + public function getProcess() + { + return $this->process; + } +} diff --git a/thinkphp/library/think/process/exception/Timeout.php b/thinkphp/library/think/process/exception/Timeout.php new file mode 100644 index 0000000..d5f1162 --- /dev/null +++ b/thinkphp/library/think/process/exception/Timeout.php @@ -0,0 +1,61 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\exception; + +use think\Process; + +class Timeout extends \RuntimeException +{ + + const TYPE_GENERAL = 1; + const TYPE_IDLE = 2; + + private $process; + private $timeoutType; + + public function __construct(Process $process, $timeoutType) + { + $this->process = $process; + $this->timeoutType = $timeoutType; + + parent::__construct(sprintf('The process "%s" exceeded the timeout of %s seconds.', $process->getCommandLine(), $this->getExceededTimeout())); + } + + public function getProcess() + { + return $this->process; + } + + public function isGeneralTimeout() + { + return $this->timeoutType === self::TYPE_GENERAL; + } + + public function isIdleTimeout() + { + return $this->timeoutType === self::TYPE_IDLE; + } + + public function getExceededTimeout() + { + switch ($this->timeoutType) { + case self::TYPE_GENERAL: + return $this->process->getTimeout(); + + case self::TYPE_IDLE: + return $this->process->getIdleTimeout(); + + default: + throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType)); + } + } +} diff --git a/thinkphp/library/think/process/pipes/Pipes.php b/thinkphp/library/think/process/pipes/Pipes.php new file mode 100644 index 0000000..82396b8 --- /dev/null +++ b/thinkphp/library/think/process/pipes/Pipes.php @@ -0,0 +1,93 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +abstract class Pipes +{ + + /** @var array */ + public $pipes = []; + + /** @var string */ + protected $inputBuffer = ''; + /** @var resource|null */ + protected $input; + + /** @var bool */ + private $blocked = true; + + const CHUNK_SIZE = 16384; + + /** + * 返回用于 proc_open 描述符的数组 + * @return array + */ + abstract public function getDescriptors(); + + /** + * 返回一个数组的索引由其相关的流,以防这些管道使用的临时文件的文件名。 + * @return string[] + */ + abstract public function getFiles(); + + /** + * 文件句柄和管道中读取数据。 + * @param bool $blocking 是否使用阻塞调用 + * @param bool $close 是否要关闭管道,如果他们已经到达 EOF。 + * @return string[] + */ + abstract public function readAndWrite($blocking, $close = false); + + /** + * 返回当前状态如果有打开的文件句柄或管道。 + * @return bool + */ + abstract public function areOpen(); + + /** + * {@inheritdoc} + */ + public function close() + { + foreach ($this->pipes as $pipe) { + fclose($pipe); + } + $this->pipes = []; + } + + /** + * 检查系统调用已被中断 + * @return bool + */ + protected function hasSystemCallBeenInterrupted() + { + $lastError = error_get_last(); + + return isset($lastError['message']) && false !== stripos($lastError['message'], 'interrupted system call'); + } + + protected function unblock() + { + if (!$this->blocked) { + return; + } + + foreach ($this->pipes as $pipe) { + stream_set_blocking($pipe, 0); + } + if (null !== $this->input) { + stream_set_blocking($this->input, 0); + } + + $this->blocked = false; + } +} diff --git a/thinkphp/library/think/process/pipes/Unix.php b/thinkphp/library/think/process/pipes/Unix.php new file mode 100644 index 0000000..fd99a5d --- /dev/null +++ b/thinkphp/library/think/process/pipes/Unix.php @@ -0,0 +1,196 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Unix extends Pipes +{ + + /** @var bool */ + private $ttyMode; + /** @var bool */ + private $ptyMode; + /** @var bool */ + private $disableOutput; + + public function __construct($ttyMode, $ptyMode, $input, $disableOutput) + { + $this->ttyMode = (bool) $ttyMode; + $this->ptyMode = (bool) $ptyMode; + $this->disableOutput = (bool) $disableOutput; + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = (string) $input; + } + } + + public function __destruct() + { + $this->close(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('/dev/null', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + if ($this->ttyMode) { + return [ + ['file', '/dev/tty', 'r'], + ['file', '/dev/tty', 'w'], + ['file', '/dev/tty', 'w'], + ]; + } + + if ($this->ptyMode && Process::isPtySupported()) { + return [ + ['pty'], + ['pty'], + ['pty'], + ]; + } + + return [ + ['pipe', 'r'], + ['pipe', 'w'], // stdout + ['pipe', 'w'], // stderr + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return []; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + + if (1 === count($this->pipes) && [0] === array_keys($this->pipes)) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + if (empty($this->pipes)) { + return []; + } + + $this->unblock(); + + $read = []; + + if (null !== $this->input) { + $r = array_merge($this->pipes, ['input' => $this->input]); + } else { + $r = $this->pipes; + } + + unset($r[0]); + + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return $read; + } + + if (0 === $n) { + return $read; + } + + foreach ($r as $pipe) { + + $type = (false !== $found = array_search($pipe, $this->pipes)) ? $found : 'input'; + $data = ''; + while ('' !== $dataread = (string) fread($pipe, self::CHUNK_SIZE)) { + $data .= $dataread; + } + + if ('' !== $data) { + if ('input' === $type) { + $this->inputBuffer .= $data; + } else { + $read[$type] = $data; + } + } + + if (false === $data || (true === $close && feof($pipe) && '' === $data)) { + if ('input' === $type) { + $this->input = null; + } else { + fclose($this->pipes[$type]); + unset($this->pipes[$type]); + } + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); // write 512k + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes; + } + + /** + * 创建一个新的 UnixPipes 实例 + * @param Process $process + * @param string|resource $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isTty(), $process->isPty(), $input, $process->isOutputDisabled()); + } +} diff --git a/thinkphp/library/think/process/pipes/Windows.php b/thinkphp/library/think/process/pipes/Windows.php new file mode 100644 index 0000000..1b8b0d4 --- /dev/null +++ b/thinkphp/library/think/process/pipes/Windows.php @@ -0,0 +1,228 @@ + +// +---------------------------------------------------------------------- + +namespace think\process\pipes; + +use think\Process; + +class Windows extends Pipes +{ + + /** @var array */ + private $files = []; + /** @var array */ + private $fileHandles = []; + /** @var array */ + private $readBytes = [ + Process::STDOUT => 0, + Process::STDERR => 0, + ]; + /** @var bool */ + private $disableOutput; + + public function __construct($disableOutput, $input) + { + $this->disableOutput = (bool) $disableOutput; + + if (!$this->disableOutput) { + + $this->files = [ + Process::STDOUT => tempnam(sys_get_temp_dir(), 'sf_proc_stdout'), + Process::STDERR => tempnam(sys_get_temp_dir(), 'sf_proc_stderr'), + ]; + foreach ($this->files as $offset => $file) { + $this->fileHandles[$offset] = fopen($this->files[$offset], 'rb'); + if (false === $this->fileHandles[$offset]) { + throw new \RuntimeException('A temporary file could not be opened to write the process output to, verify that your TEMP environment variable is writable'); + } + } + } + + if (is_resource($input)) { + $this->input = $input; + } else { + $this->inputBuffer = $input; + } + } + + public function __destruct() + { + $this->close(); + $this->removeFiles(); + } + + /** + * {@inheritdoc} + */ + public function getDescriptors() + { + if ($this->disableOutput) { + $nullstream = fopen('NUL', 'c'); + + return [ + ['pipe', 'r'], + $nullstream, + $nullstream, + ]; + } + + return [ + ['pipe', 'r'], + ['file', 'NUL', 'w'], + ['file', 'NUL', 'w'], + ]; + } + + /** + * {@inheritdoc} + */ + public function getFiles() + { + return $this->files; + } + + /** + * {@inheritdoc} + */ + public function readAndWrite($blocking, $close = false) + { + $this->write($blocking, $close); + + $read = []; + $fh = $this->fileHandles; + foreach ($fh as $type => $fileHandle) { + if (0 !== fseek($fileHandle, $this->readBytes[$type])) { + continue; + } + $data = ''; + $dataread = null; + while (!feof($fileHandle)) { + if (false !== $dataread = fread($fileHandle, self::CHUNK_SIZE)) { + $data .= $dataread; + } + } + if (0 < $length = strlen($data)) { + $this->readBytes[$type] += $length; + $read[$type] = $data; + } + + if (false === $dataread || (true === $close && feof($fileHandle) && '' === $data)) { + fclose($this->fileHandles[$type]); + unset($this->fileHandles[$type]); + } + } + + return $read; + } + + /** + * {@inheritdoc} + */ + public function areOpen() + { + return (bool) $this->pipes && (bool) $this->fileHandles; + } + + /** + * {@inheritdoc} + */ + public function close() + { + parent::close(); + foreach ($this->fileHandles as $handle) { + fclose($handle); + } + $this->fileHandles = []; + } + + /** + * 创建一个新的 WindowsPipes 实例。 + * @param Process $process + * @param $input + * @return self + */ + public static function create(Process $process, $input) + { + return new static($process->isOutputDisabled(), $input); + } + + /** + * 删除临时文件 + */ + private function removeFiles() + { + foreach ($this->files as $filename) { + if (file_exists($filename)) { + @unlink($filename); + } + } + $this->files = []; + } + + /** + * 写入到 stdin 输入 + * @param bool $blocking + * @param bool $close + */ + private function write($blocking, $close) + { + if (empty($this->pipes)) { + return; + } + + $this->unblock(); + + $r = null !== $this->input ? ['input' => $this->input] : null; + $w = isset($this->pipes[0]) ? [$this->pipes[0]] : null; + $e = null; + + if (false === $n = @stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) { + if (!$this->hasSystemCallBeenInterrupted()) { + $this->pipes = []; + } + + return; + } + + if (0 === $n) { + return; + } + + if (null !== $r && 0 < count($r)) { + $data = ''; + while ($dataread = fread($r['input'], self::CHUNK_SIZE)) { + $data .= $dataread; + } + + $this->inputBuffer .= $data; + + if (false === $data || (true === $close && feof($r['input']) && '' === $data)) { + $this->input = null; + } + } + + if (null !== $w && 0 < count($w)) { + while (strlen($this->inputBuffer)) { + $written = fwrite($w[0], $this->inputBuffer, 2 << 18); + if ($written > 0) { + $this->inputBuffer = (string) substr($this->inputBuffer, $written); + } else { + break; + } + } + } + + if ('' === $this->inputBuffer && null === $this->input && isset($this->pipes[0])) { + fclose($this->pipes[0]); + unset($this->pipes[0]); + } + } +} diff --git a/thinkphp/library/think/response/Download.php b/thinkphp/library/think/response/Download.php new file mode 100644 index 0000000..d5fcb44 --- /dev/null +++ b/thinkphp/library/think/response/Download.php @@ -0,0 +1,148 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Exception; +use think\Response; + +class Download extends Response +{ + protected $expire = 360; + protected $name; + protected $mimeType; + protected $isContent = false; + protected $openinBrower = false; + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + if (!$this->isContent && !is_file($data)) { + throw new Exception('file not exists:' . $data); + } + + ob_end_clean(); + + if (!empty($this->name)) { + $name = $this->name; + } else { + $name = !$this->isContent ? pathinfo($data, PATHINFO_BASENAME) : ''; + } + + if ($this->isContent) { + $mimeType = $this->mimeType; + $size = strlen($data); + } else { + $mimeType = $this->getMimeType($data); + $size = filesize($data); + } + + $this->header['Pragma'] = 'public'; + $this->header['Content-Type'] = $mimeType ?: 'application/octet-stream'; + $this->header['Cache-control'] = 'max-age=' . $this->expire; + $this->header['Content-Disposition'] = $this->openinBrower ? 'inline' : 'attachment; filename="' . $name . '"'; + $this->header['Content-Length'] = $size; + $this->header['Content-Transfer-Encoding'] = 'binary'; + $this->header['Expires'] = gmdate("D, d M Y H:i:s", time() + $this->expire) . ' GMT'; + + $this->lastModified(gmdate('D, d M Y H:i:s', time()) . ' GMT'); + + $data = $this->isContent ? $data : file_get_contents($data); + return $data; + } + + /** + * 设置是否为内容 必须配合mimeType方法使用 + * @access public + * @param bool $content + * @return $this + */ + public function isContent($content = true) + { + $this->isContent = $content; + return $this; + } + + /** + * 设置有效期 + * @access public + * @param integer $expire 有效期 + * @return $this + */ + public function expire($expire) + { + $this->expire = $expire; + return $this; + } + + /** + * 设置文件类型 + * @access public + * @param string $filename 文件名 + * @return $this + */ + public function mimeType($mimeType) + { + $this->mimeType = $mimeType; + return $this; + } + + /** + * 获取文件类型信息 + * @access public + * @param string $filename 文件名 + * @return string + */ + protected function getMimeType($filename) + { + if (!empty($this->mimeType)) { + return $this->mimeType; + } + + $finfo = finfo_open(FILEINFO_MIME_TYPE); + + return finfo_file($finfo, $filename); + } + + /** + * 设置下载文件的显示名称 + * @access public + * @param string $filename 文件名 + * @param bool $extension 后缀自动识别 + * @return $this + */ + public function name($filename, $extension = true) + { + $this->name = $filename; + + if ($extension && false === strpos($filename, '.')) { + $this->name .= '.' . pathinfo($this->data, PATHINFO_EXTENSION); + } + + return $this; + } + + /** + * 设置是否在浏览器中显示文件 + * @access public + * @param bool $openinBrower 是否在浏览器中显示文件 + * @return $this + */ + public function openinBrower($openinBrower) { + $this->openinBrower = $openinBrower; + return $this; + } +} diff --git a/thinkphp/library/think/response/Json.php b/thinkphp/library/think/response/Json.php new file mode 100644 index 0000000..aa5bbd6 --- /dev/null +++ b/thinkphp/library/think/response/Json.php @@ -0,0 +1,51 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Json extends Response +{ + // 输出参数 + protected $options = [ + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/json'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Jsonp.php b/thinkphp/library/think/response/Jsonp.php new file mode 100644 index 0000000..f69e88e --- /dev/null +++ b/thinkphp/library/think/response/Jsonp.php @@ -0,0 +1,58 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Jsonp extends Response +{ + // 输出参数 + protected $options = [ + 'var_jsonp_handler' => 'callback', + 'default_jsonp_handler' => 'jsonpReturn', + 'json_encode_param' => JSON_UNESCAPED_UNICODE, + ]; + + protected $contentType = 'application/javascript'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + try { + // 返回JSON数据格式到客户端 包含状态信息 [当url_common_param为false时是无法获取到$_GET的数据的,故使用Request来获取] + $var_jsonp_handler = $this->app['request']->param($this->options['var_jsonp_handler'], ""); + $handler = !empty($var_jsonp_handler) ? $var_jsonp_handler : $this->options['default_jsonp_handler']; + + $data = json_encode($data, $this->options['json_encode_param']); + + if (false === $data) { + throw new \InvalidArgumentException(json_last_error_msg()); + } + + $data = $handler . '(' . $data . ');'; + + return $data; + } catch (\Exception $e) { + if ($e->getPrevious()) { + throw $e->getPrevious(); + } + throw $e; + } + } + +} diff --git a/thinkphp/library/think/response/Jump.php b/thinkphp/library/think/response/Jump.php new file mode 100644 index 0000000..258448c --- /dev/null +++ b/thinkphp/library/think/response/Jump.php @@ -0,0 +1,32 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Jump extends Response +{ + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + * @throws \Exception + */ + protected function output($data) + { + $data = $this->app['view']->fetch($this->options['jump_template'], $data); + return $data; + } +} diff --git a/thinkphp/library/think/response/Redirect.php b/thinkphp/library/think/response/Redirect.php new file mode 100644 index 0000000..73729ce --- /dev/null +++ b/thinkphp/library/think/response/Redirect.php @@ -0,0 +1,118 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class Redirect extends Response +{ + + protected $options = []; + + // URL参数 + protected $params = []; + + public function __construct($data = '', $code = 302, array $header = [], array $options = []) + { + parent::__construct($data, $code, $header, $options); + + $this->cacheControl('no-cache,must-revalidate'); + } + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + $this->header['Location'] = $this->getTargetUrl(); + + return; + } + + /** + * 重定向传值(通过Session) + * @access protected + * @param string|array $name 变量名或者数组 + * @param mixed $value 值 + * @return $this + */ + public function with($name, $value = null) + { + $session = $this->app['session']; + + if (is_array($name)) { + foreach ($name as $key => $val) { + $session->flash($key, $val); + } + } else { + $session->flash($name, $value); + } + + return $this; + } + + /** + * 获取跳转地址 + * @access public + * @return string + */ + public function getTargetUrl() + { + if (strpos($this->data, '://') || (0 === strpos($this->data, '/') && empty($this->params))) { + return $this->data; + } else { + return $this->app['url']->build($this->data, $this->params); + } + } + + public function params($params = []) + { + $this->params = $params; + + return $this; + } + + /** + * 记住当前url后跳转 + * @access public + * @return $this + */ + public function remember() + { + $this->app['session']->set('redirect_url', $this->app['request']->url()); + + return $this; + } + + /** + * 跳转到上次记住的url + * @access public + * @param string $url 闪存数据不存在时的跳转地址 + * @return $this + */ + public function restore($url = null) + { + $session = $this->app['session']; + + if ($session->has('redirect_url')) { + $this->data = $session->get('redirect_url'); + $session->delete('redirect_url'); + } elseif ($url) { + $this->data = $url; + } + + return $this; + } +} diff --git a/thinkphp/library/think/response/View.php b/thinkphp/library/think/response/View.php new file mode 100644 index 0000000..c836ccb --- /dev/null +++ b/thinkphp/library/think/response/View.php @@ -0,0 +1,94 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Response; + +class View extends Response +{ + // 输出参数 + protected $options = []; + protected $vars = []; + protected $filter; + protected $contentType = 'text/html'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + // 渲染模板输出 + return $this->app['view'] + ->filter($this->filter) + ->fetch($data, $this->vars); + } + + /** + * 获取视图变量 + * @access public + * @param string $name 模板变量 + * @return mixed + */ + public function getVars($name = null) + { + if (is_null($name)) { + return $this->vars; + } else { + return isset($this->vars[$name]) ? $this->vars[$name] : null; + } + } + + /** + * 模板变量赋值 + * @access public + * @param mixed $name 变量名 + * @param mixed $value 变量值 + * @return $this + */ + public function assign($name, $value = '') + { + if (is_array($name)) { + $this->vars = array_merge($this->vars, $name); + } else { + $this->vars[$name] = $value; + } + + return $this; + } + + /** + * 视图内容过滤 + * @access public + * @param callable $filter + * @return $this + */ + public function filter($filter) + { + $this->filter = $filter; + return $this; + } + + /** + * 检查模板是否存在 + * @access private + * @param string|array $name 参数名 + * @return bool + */ + public function exists($name) + { + return $this->app['view']->exists($name); + } + +} diff --git a/thinkphp/library/think/response/Xml.php b/thinkphp/library/think/response/Xml.php new file mode 100644 index 0000000..9c1681a --- /dev/null +++ b/thinkphp/library/think/response/Xml.php @@ -0,0 +1,116 @@ + +// +---------------------------------------------------------------------- + +namespace think\response; + +use think\Collection; +use think\Model; +use think\Response; + +class Xml extends Response +{ + // 输出参数 + protected $options = [ + // 根节点名 + 'root_node' => 'think', + // 根节点属性 + 'root_attr' => '', + //数字索引的子节点名 + 'item_node' => 'item', + // 数字索引子节点key转换的属性名 + 'item_key' => 'id', + // 数据编码 + 'encoding' => 'utf-8', + ]; + + protected $contentType = 'text/xml'; + + /** + * 处理数据 + * @access protected + * @param mixed $data 要处理的数据 + * @return mixed + */ + protected function output($data) + { + if (is_string($data)) { + if (0 !== strpos($data, 'options['encoding']; + $xml = ""; + $data = $xml . $data; + } + return $data; + } + + // XML数据转换 + return $this->xmlEncode($data, $this->options['root_node'], $this->options['item_node'], $this->options['root_attr'], $this->options['item_key'], $this->options['encoding']); + } + + /** + * XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $root 根节点名 + * @param string $item 数字索引的子节点名 + * @param string $attr 根节点属性 + * @param string $id 数字索引子节点key转换的属性名 + * @param string $encoding 数据编码 + * @return string + */ + protected function xmlEncode($data, $root, $item, $attr, $id, $encoding) + { + if (is_array($attr)) { + $array = []; + foreach ($attr as $key => $value) { + $array[] = "{$key}=\"{$value}\""; + } + $attr = implode(' ', $array); + } + + $attr = trim($attr); + $attr = empty($attr) ? '' : " {$attr}"; + $xml = ""; + $xml .= "<{$root}{$attr}>"; + $xml .= $this->dataToXml($data, $item, $id); + $xml .= ""; + + return $xml; + } + + /** + * 数据XML编码 + * @access protected + * @param mixed $data 数据 + * @param string $item 数字索引时的节点名称 + * @param string $id 数字索引key转换为的属性名 + * @return string + */ + protected function dataToXml($data, $item, $id) + { + $xml = $attr = ''; + + if ($data instanceof Collection || $data instanceof Model) { + $data = $data->toArray(); + } + + foreach ($data as $key => $val) { + if (is_numeric($key)) { + $id && $attr = " {$id}=\"{$key}\""; + $key = $item; + } + $xml .= "<{$key}{$attr}>"; + $xml .= (is_array($val) || is_object($val)) ? $this->dataToXml($val, $item, $id) : $val; + $xml .= ""; + } + + return $xml; + } +} diff --git a/thinkphp/library/think/route/AliasRule.php b/thinkphp/library/think/route/AliasRule.php new file mode 100644 index 0000000..393cb31 --- /dev/null +++ b/thinkphp/library/think/route/AliasRule.php @@ -0,0 +1,119 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class AliasRule extends Domain +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由别名 + * @param string $route 路由绑定 + * @param array $option 路由参数 + */ + public function __construct(Route $router, RuleGroup $parent, $name, $route, $option = []) + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->option = $option; + } + + /** + * 检测路由别名 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + list($action, $bind) = array_pad(explode('|', $url, 2), 2, ''); + + if (isset($this->option['allow']) && !in_array($action, $this->option['allow'])) { + // 允许操作 + return false; + } elseif (isset($this->option['except']) && in_array($action, $this->option['except'])) { + // 排除操作 + return false; + } + + if (isset($this->option['method'][$action])) { + $this->option['method'] = $this->option['method'][$action]; + } + + // 匹配后执行的行为 + $this->afterMatchGroup($request); + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + } + + if (isset($this->option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $bind = preg_replace('/\.(' . $request->ext() . ')$/i', '', $bind); + } + + $this->parseBindAppendParam($this->route); + + if (0 === strpos($this->route, '\\')) { + // 路由到类 + return $this->bindToClass($request, $bind, substr($this->route, 1)); + } elseif (0 === strpos($this->route, '@')) { + // 路由到控制器类 + return $this->bindToController($request, $bind, substr($this->route, 1)); + } else { + // 路由到模块/控制器 + return $this->bindToModule($request, $bind, $this->route); + } + } + + /** + * 设置允许的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ + public function allow($action = []) + { + return $this->option('allow', $action); + } + + /** + * 设置排除的操作方法 + * @access public + * @param array $action 操作方法 + * @return $this + */ + public function except($action = []) + { + return $this->option('except', $action); + } + +} diff --git a/thinkphp/library/think/route/Dispatch.php b/thinkphp/library/think/route/Dispatch.php new file mode 100644 index 0000000..93afe73 --- /dev/null +++ b/thinkphp/library/think/route/Dispatch.php @@ -0,0 +1,365 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\exception\ValidateException; +use think\App; +use think\Request; +use think\Response; + +abstract class Dispatch +{ + /** + * 应用对象 + * @var App + */ + protected $app; + + /** + * 请求对象 + * @var Request + */ + protected $request; + + /** + * 路由规则 + * @var Rule + */ + protected $rule; + + /** + * 调度信息 + * @var mixed + */ + protected $dispatch; + + /** + * 调度参数 + * @var array + */ + protected $param; + + /** + * 状态码 + * @var string + */ + protected $code; + + /** + * 是否进行大小写转换 + * @var bool + */ + protected $convert; + + public function __construct(Request $request, Rule $rule, $dispatch, $param = [], $code = null) + { + $this->request = $request; + $this->rule = $rule; + $this->app = Container::get('app'); + $this->dispatch = $dispatch; + $this->param = $param; + $this->code = $code; + + if (isset($param['convert'])) { + $this->convert = $param['convert']; + } + } + + public function init() + { + // 执行路由后置操作 + if ($this->rule->doAfter()) { + // 设置请求的路由信息 + + // 设置当前请求的参数 + $this->request->setRouteVars($this->rule->getVars()); + $this->request->routeInfo([ + 'rule' => $this->rule->getRule(), + 'route' => $this->rule->getRoute(), + 'option' => $this->rule->getOption(), + 'var' => $this->rule->getVars(), + ]); + + $this->doRouteAfter(); + } + + return $this; + } + + /** + * 检查路由后置操作 + * @access protected + * @return void + */ + protected function doRouteAfter() + { + // 记录匹配的路由信息 + $option = $this->rule->getOption(); + $matches = $this->rule->getVars(); + + // 添加中间件 + if (!empty($option['middleware'])) { + $this->app['middleware']->import($option['middleware']); + } + + // 绑定模型数据 + if (!empty($option['model'])) { + $this->createBindModel($option['model'], $matches); + } + + // 指定Header数据 + if (!empty($option['header'])) { + $header = $option['header']; + $this->app['hook']->add('response_send', function ($response) use ($header) { + $response->header($header); + }); + } + + // 指定Response响应数据 + if (!empty($option['response'])) { + foreach ($option['response'] as $response) { + $this->app['hook']->add('response_send', $response); + } + } + + // 开启请求缓存 + if (isset($option['cache']) && $this->request->isGet()) { + $this->parseRequestCache($option['cache']); + } + + if (!empty($option['append'])) { + $this->request->setRouteVars($option['append']); + } + } + + /** + * 执行路由调度 + * @access public + * @return mixed + */ + public function run() + { + $option = $this->rule->getOption(); + + // 检测路由after行为 + if (!empty($option['after'])) { + $dispatch = $this->checkAfter($option['after']); + + if ($dispatch instanceof Response) { + return $dispatch; + } + } + + // 数据自动验证 + if (isset($option['validate'])) { + $this->autoValidate($option['validate']); + } + + $data = $this->exec(); + + return $this->autoResponse($data); + } + + protected function autoResponse($data) + { + if ($data instanceof Response) { + $response = $data; + } elseif (!is_null($data)) { + // 默认自动识别响应输出类型 + $isAjax = $this->request->isAjax(); + $type = $isAjax ? $this->rule->getConfig('default_ajax_return') : $this->rule->getConfig('default_return_type'); + + $response = Response::create($data, $type); + } else { + $data = ob_get_clean(); + $content = false === $data ? '' : $data; + $status = '' === $content && $this->request->isAjax() ? 204 : 200; + $response = Response::create($content, '', $status); + } + + return $response; + } + + /** + * 检查路由后置行为 + * @access protected + * @param mixed $after 后置行为 + * @return mixed + */ + protected function checkAfter($after) + { + $this->app['log']->notice('路由后置行为建议使用中间件替代!'); + + $hook = $this->app['hook']; + + $result = null; + + foreach ((array) $after as $behavior) { + $result = $hook->exec($behavior); + + if (!is_null($result)) { + break; + } + } + + // 路由规则重定向 + if ($result instanceof Response) { + return $result; + } + + return false; + } + + /** + * 验证数据 + * @access protected + * @param array $option + * @return void + * @throws ValidateException + */ + protected function autoValidate($option) + { + list($validate, $scene, $message, $batch) = $option; + + if (is_array($validate)) { + // 指定验证规则 + $v = $this->app->validate(); + $v->rule($validate); + } else { + // 调用验证器 + $v = $this->app->validate($validate); + if (!empty($scene)) { + $v->scene($scene); + } + } + + if (!empty($message)) { + $v->message($message); + } + + // 批量验证 + if ($batch) { + $v->batch(true); + } + + if (!$v->check($this->request->param())) { + throw new ValidateException($v->getError()); + } + } + + /** + * 处理路由请求缓存 + * @access protected + * @param Request $request 请求对象 + * @param string|array $cache 路由缓存 + * @return void + */ + protected function parseRequestCache($cache) + { + if (is_array($cache)) { + list($key, $expire, $tag) = array_pad($cache, 3, null); + } else { + $key = str_replace('|', '/', $this->request->url()); + $expire = $cache; + $tag = null; + } + + $cache = $this->request->cache($key, $expire, $tag); + $this->app->setResponseCache($cache); + } + + /** + * 路由绑定模型实例 + * @access protected + * @param array|\Clousre $bindModel 绑定模型 + * @param array $matches 路由变量 + * @return void + */ + protected function createBindModel($bindModel, $matches) + { + foreach ($bindModel as $key => $val) { + if ($val instanceof \Closure) { + $result = $this->app->invokeFunction($val, $matches); + } else { + $fields = explode('&', $key); + + if (is_array($val)) { + list($model, $exception) = $val; + } else { + $model = $val; + $exception = true; + } + + $where = []; + $match = true; + + foreach ($fields as $field) { + if (!isset($matches[$field])) { + $match = false; + break; + } else { + $where[] = [$field, '=', $matches[$field]]; + } + } + + if ($match) { + $query = strpos($model, '\\') ? $model::where($where) : $this->app->model($model)->where($where); + $result = $query->failException($exception)->find(); + } + } + + if (!empty($result)) { + // 注入容器 + $this->app->instance(get_class($result), $result); + } + } + } + + public function convert($convert) + { + $this->convert = $convert; + + return $this; + } + + public function getDispatch() + { + return $this->dispatch; + } + + public function getParam() + { + return $this->param; + } + + abstract public function exec(); + + public function __sleep() + { + return ['rule', 'dispatch', 'convert', 'param', 'code', 'controller', 'actionName']; + } + + public function __wakeup() + { + $this->app = Container::get('app'); + $this->request = $this->app['request']; + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['app'], $data['request'], $data['rule']); + + return $data; + } +} diff --git a/thinkphp/library/think/route/Domain.php b/thinkphp/library/think/route/Domain.php new file mode 100644 index 0000000..80950dc --- /dev/null +++ b/thinkphp/library/think/route/Domain.php @@ -0,0 +1,236 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Loader; +use think\Route; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Module as ModuleDispatch; + +class Domain extends RuleGroup +{ + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param string $name 路由域名 + * @param mixed $rule 域名路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, $name = '', $rule = null, $option = [], $pattern = []) + { + $this->router = $router; + $this->domain = $name; + $this->option = $option; + $this->rule = $rule; + $this->pattern = $pattern; + } + + /** + * 检测域名路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + // 检测别名路由 + $result = $this->checkRouteAlias($request, $url); + + if (false !== $result) { + return $result; + } + + // 检测URL绑定 + $result = $this->checkUrlBind($request, $url); + + if (!empty($this->option['append'])) { + $request->setRouteVars($this->option['append']); + unset($this->option['append']); + } + + if (false !== $result) { + return $result; + } + + // 添加域名中间件 + if (!empty($this->option['middleware'])) { + Container::get('middleware')->import($this->option['middleware']); + unset($this->option['middleware']); + } + + return parent::check($request, $url, $completeMatch); + } + + /** + * 设置路由绑定 + * @access public + * @param string $bind 绑定信息 + * @return $this + */ + public function bind($bind) + { + $this->router->bind($bind, $this->domain); + return $this; + } + + /** + * 检测路由别名 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkRouteAlias($request, $url) + { + $alias = strpos($url, '|') ? strstr($url, '|', true) : $url; + + $item = $this->router->getAlias($alias); + + return $item ? $item->check($request, $url) : false; + } + + /** + * 检测URL绑定 + * @access private + * @param Request $request + * @param string $url URL地址 + * @return Dispatch|false + */ + private function checkUrlBind($request, $url) + { + $bind = $this->router->getBind($this->domain); + + if (!empty($bind)) { + $this->parseBindAppendParam($bind); + + // 记录绑定信息 + Container::get('app')->log('[ BIND ] ' . var_export($bind, true)); + + // 如果有URL绑定 则进行绑定检测 + $type = substr($bind, 0, 1); + $bind = substr($bind, 1); + + $bindTo = [ + '\\' => 'bindToClass', + '@' => 'bindToController', + ':' => 'bindToNamespace', + ]; + + if (isset($bindTo[$type])) { + return $this->{$bindTo[$type]}($request, $url, $bind); + } + } + + return false; + } + + protected function parseBindAppendParam(&$bind) + { + if (false !== strpos($bind, '?')) { + list($bind, $query) = explode('?', $bind); + parse_str($query, $vars); + $this->append($vars); + } + } + + /** + * 绑定到类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $class 类名(带命名空间) + * @return CallbackDispatch + */ + protected function bindToClass($request, $url, $class) + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($request, $array[1], $param); + } + + return new CallbackDispatch($request, $this, [$class, $action], $param); + } + + /** + * 绑定到命名空间 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $namespace 命名空间 + * @return CallbackDispatch + */ + protected function bindToNamespace($request, $url, $namespace) + { + $array = explode('|', $url, 3); + $class = !empty($array[0]) ? $array[0] : $this->router->config('default_controller'); + $method = !empty($array[1]) ? $array[1] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[2])) { + $this->parseUrlParams($request, $array[2], $param); + } + + return new CallbackDispatch($request, $this, [$namespace . '\\' . Loader::parseName($class, 1), $method], $param); + } + + /** + * 绑定到控制器类 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器名 (支持带模块名 index/user ) + * @return ControllerDispatch + */ + protected function bindToController($request, $url, $controller) + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($request, $array[1], $param); + } + + return new ControllerDispatch($request, $this, $controller . '/' . $action, $param); + } + + /** + * 绑定到模块/控制器 + * @access protected + * @param Request $request + * @param string $url URL地址 + * @param string $controller 控制器类名(带命名空间) + * @return ModuleDispatch + */ + protected function bindToModule($request, $url, $controller) + { + $array = explode('|', $url, 2); + $action = !empty($array[0]) ? $array[0] : $this->router->config('default_action'); + $param = []; + + if (!empty($array[1])) { + $this->parseUrlParams($request, $array[1], $param); + } + + return new ModuleDispatch($request, $this, $controller . '/' . $action, $param); + } + +} diff --git a/thinkphp/library/think/route/Resource.php b/thinkphp/library/think/route/Resource.php new file mode 100644 index 0000000..ff13928 --- /dev/null +++ b/thinkphp/library/think/route/Resource.php @@ -0,0 +1,126 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Route; + +class Resource extends RuleGroup +{ + // 资源路由名称 + protected $resource; + + // REST路由方法定义 + protected $rest = []; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 资源名称 + * @param string $route 路由地址 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @param array $rest 资源定义 + */ + public function __construct(Route $router, RuleGroup $parent = null, $name = '', $route = '', $option = [], $pattern = [], $rest = []) + { + $this->router = $router; + $this->parent = $parent; + $this->resource = $name; + $this->route = $route; + $this->name = strpos($name, '.') ? strstr($name, '.', true) : $name; + + $this->setFullName(); + + // 资源路由默认为完整匹配 + $option['complete_match'] = true; + + $this->pattern = $pattern; + $this->option = $option; + $this->rest = $rest; + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if ($router->isTest()) { + $this->buildResourceRule(); + } + } + + /** + * 生成资源路由规则 + * @access protected + * @return void + */ + protected function buildResourceRule() + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + $rule = $this->resource; + $option = $this->option; + + if (strpos($rule, '.')) { + // 注册嵌套资源路由 + $array = explode('.', $rule); + $last = array_pop($array); + $item = []; + + foreach ($array as $val) { + $item[] = $val . '/<' . (isset($option['var'][$val]) ? $option['var'][$val] : $val . '_id') . '>'; + } + + $rule = implode('/', $item) . '/' . $last; + } + + $prefix = substr($rule, strlen($this->name) + 1); + + // 注册资源路由 + foreach ($this->rest as $key => $val) { + if ((isset($option['only']) && !in_array($key, $option['only'])) + || (isset($option['except']) && in_array($key, $option['except']))) { + continue; + } + + if (isset($last) && strpos($val[1], '') && isset($option['var'][$last])) { + $val[1] = str_replace('', '<' . $option['var'][$last] . '>', $val[1]); + } elseif (strpos($val[1], '') && isset($option['var'][$rule])) { + $val[1] = str_replace('', '<' . $option['var'][$rule] . '>', $val[1]); + } + + $this->addRule(trim($prefix . $val[1], '/'), $this->route . '/' . $val[2], $val[0]); + } + + $this->router->setGroup($origin); + } + + /** + * rest方法定义和修改 + * @access public + * @param string $name 方法名称 + * @param array|bool $resource 资源 + * @return $this + */ + public function rest($name, $resource = []) + { + if (is_array($name)) { + $this->rest = $resource ? $name : array_merge($this->rest, $name); + } else { + $this->rest[$name] = $resource; + } + + return $this; + } +} diff --git a/thinkphp/library/think/route/Rule.php b/thinkphp/library/think/route/Rule.php new file mode 100644 index 0000000..35730fe --- /dev/null +++ b/thinkphp/library/think/route/Rule.php @@ -0,0 +1,1117 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Request; +use think\Response; +use think\route\dispatch\Callback as CallbackDispatch; +use think\route\dispatch\Controller as ControllerDispatch; +use think\route\dispatch\Module as ModuleDispatch; +use think\route\dispatch\Redirect as RedirectDispatch; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\View as ViewDispatch; + +abstract class Rule +{ + /** + * 路由标识 + * @var string + */ + protected $name; + + /** + * 路由对象 + * @var Route + */ + protected $router; + + /** + * 路由所属分组 + * @var RuleGroup + */ + protected $parent; + + /** + * 路由规则 + * @var mixed + */ + protected $rule; + + /** + * 路由地址 + * @var string|\Closure + */ + protected $route; + + /** + * 请求类型 + * @var string + */ + protected $method; + + /** + * 路由变量 + * @var array + */ + protected $vars = []; + + /** + * 路由参数 + * @var array + */ + protected $option = []; + + /** + * 路由变量规则 + * @var array + */ + protected $pattern = []; + + /** + * 需要和分组合并的路由参数 + * @var array + */ + protected $mergeOptions = ['after', 'model', 'header', 'response', 'append', 'middleware']; + + /** + * 是否需要后置操作 + * @var bool + */ + protected $doAfter; + + /** + * 是否锁定参数 + * @var bool + */ + protected $lockOption = false; + + abstract public function check($request, $url, $completeMatch = false); + + /** + * 获取Name + * @access public + * @return string + */ + public function getName() + { + return $this->name; + } + + /** + * 获取当前路由规则 + * @access public + * @return string + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取当前路由地址 + * @access public + * @return mixed + */ + public function getRoute() + { + return $this->route; + } + + /** + * 获取当前路由的请求类型 + * @access public + * @return string + */ + public function getMethod() + { + return strtolower($this->method); + } + + /** + * 获取当前路由的变量 + * @access public + * @return array + */ + public function getVars() + { + return $this->vars; + } + + /** + * 获取路由对象 + * @access public + * @return Route + */ + public function getRouter() + { + return $this->router; + } + + /** + * 路由是否有后置操作 + * @access public + * @return bool + */ + public function doAfter() + { + return $this->doAfter; + } + + /** + * 获取路由分组 + * @access public + * @return RuleGroup|null + */ + public function getParent() + { + return $this->parent; + } + + /** + * 获取路由所在域名 + * @access public + * @return string + */ + public function getDomain() + { + return $this->parent->getDomain(); + } + + /** + * 获取变量规则定义 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getPattern($name = '') + { + if ('' === $name) { + return $this->pattern; + } + + return isset($this->pattern[$name]) ? $this->pattern[$name] : null; + } + + /** + * 获取路由参数 + * @access public + * @param string $name 变量名 + * @return mixed + */ + public function getConfig($name = '') + { + return $this->router->config($name); + } + + /** + * 获取路由参数定义 + * @access public + * @param string $name 参数名 + * @return mixed + */ + public function getOption($name = '') + { + if ('' === $name) { + return $this->option; + } + + return isset($this->option[$name]) ? $this->option[$name] : null; + } + + /** + * 注册路由参数 + * @access public + * @param string|array $name 参数名 + * @param mixed $value 值 + * @return $this + */ + public function option($name, $value = '') + { + if (is_array($name)) { + $this->option = array_merge($this->option, $name); + } else { + $this->option[$name] = $value; + } + + return $this; + } + + /** + * 注册变量规则 + * @access public + * @param string|array $name 变量名 + * @param string $rule 变量规则 + * @return $this + */ + public function pattern($name, $rule = '') + { + if (is_array($name)) { + $this->pattern = array_merge($this->pattern, $name); + } else { + $this->pattern[$name] = $rule; + } + + return $this; + } + + /** + * 设置标识 + * @access public + * @param string $name 标识名 + * @return $this + */ + public function name($name) + { + $this->name = $name; + + return $this; + } + + /** + * 设置变量 + * @access public + * @param array $vars 变量 + * @return $this + */ + public function vars($vars) + { + $this->vars = $vars; + + return $this; + } + + /** + * 设置路由请求类型 + * @access public + * @param string $method + * @return $this + */ + public function method($method) + { + return $this->option('method', strtolower($method)); + } + + /** + * 设置路由前置行为 + * @access public + * @param array|\Closure $before + * @return $this + */ + public function before($before) + { + return $this->option('before', $before); + } + + /** + * 设置路由后置行为 + * @access public + * @param array|\Closure $after + * @return $this + */ + public function after($after) + { + return $this->option('after', $after); + } + + /** + * 检查后缀 + * @access public + * @param string $ext + * @return $this + */ + public function ext($ext = '') + { + return $this->option('ext', $ext); + } + + /** + * 检查禁止后缀 + * @access public + * @param string $ext + * @return $this + */ + public function denyExt($ext = '') + { + return $this->option('deny_ext', $ext); + } + + /** + * 检查域名 + * @access public + * @param string $domain + * @return $this + */ + public function domain($domain) + { + return $this->option('domain', $domain); + } + + /** + * 设置参数过滤检查 + * @access public + * @param string|array $name + * @param mixed $value + * @return $this + */ + public function filter($name, $value = null) + { + if (is_array($name)) { + $this->option['filter'] = $name; + } else { + $this->option['filter'][$name] = $value; + } + + return $this; + } + + /** + * 绑定模型 + * @access public + * @param array|string $var 路由变量名 多个使用 & 分割 + * @param string|\Closure $model 绑定模型类 + * @param bool $exception 是否抛出异常 + * @return $this + */ + public function model($var, $model = null, $exception = true) + { + if ($var instanceof \Closure) { + $this->option['model'][] = $var; + } elseif (is_array($var)) { + $this->option['model'] = $var; + } elseif (is_null($model)) { + $this->option['model']['id'] = [$var, true]; + } else { + $this->option['model'][$var] = [$model, $exception]; + } + + return $this; + } + + /** + * 附加路由隐式参数 + * @access public + * @param array $append + * @return $this + */ + public function append(array $append = []) + { + if (isset($this->option['append'])) { + $this->option['append'] = array_merge($this->option['append'], $append); + } else { + $this->option['append'] = $append; + } + + return $this; + } + + /** + * 绑定验证 + * @access public + * @param mixed $validate 验证器类 + * @param string $scene 验证场景 + * @param array $message 验证提示 + * @param bool $batch 批量验证 + * @return $this + */ + public function validate($validate, $scene = null, $message = [], $batch = false) + { + $this->option['validate'] = [$validate, $scene, $message, $batch]; + + return $this; + } + + /** + * 绑定Response对象 + * @access public + * @param mixed $response + * @return $this + */ + public function response($response) + { + $this->option['response'][] = $response; + return $this; + } + + /** + * 设置Response Header信息 + * @access public + * @param string|array $name 参数名 + * @param string $value 参数值 + * @return $this + */ + public function header($header, $value = null) + { + if (is_array($header)) { + $this->option['header'] = $header; + } else { + $this->option['header'][$header] = $value; + } + + return $this; + } + + /** + * 指定路由中间件 + * @access public + * @param string|array|\Closure $middleware + * @param mixed $param + * @return $this + */ + public function middleware($middleware, $param = null) + { + if (is_null($param) && is_array($middleware)) { + $this->option['middleware'] = $middleware; + } else { + foreach ((array) $middleware as $item) { + $this->option['middleware'][] = [$item, $param]; + } + } + + return $this; + } + + /** + * 设置路由缓存 + * @access public + * @param array|string $cache + * @return $this + */ + public function cache($cache) + { + return $this->option('cache', $cache); + } + + /** + * 检查URL分隔符 + * @access public + * @param bool $depr + * @return $this + */ + public function depr($depr) + { + return $this->option('param_depr', $depr); + } + + /** + * 是否合并额外参数 + * @access public + * @param bool $merge + * @return $this + */ + public function mergeExtraVars($merge = true) + { + return $this->option('merge_extra_vars', $merge); + } + + /** + * 设置需要合并的路由参数 + * @access public + * @param array $option + * @return $this + */ + public function mergeOptions($option = []) + { + $this->mergeOptions = array_merge($this->mergeOptions, $option); + return $this; + } + + /** + * 检查是否为HTTPS请求 + * @access public + * @param bool $https + * @return $this + */ + public function https($https = true) + { + return $this->option('https', $https); + } + + /** + * 检查是否为AJAX请求 + * @access public + * @param bool $ajax + * @return $this + */ + public function ajax($ajax = true) + { + return $this->option('ajax', $ajax); + } + + /** + * 检查是否为PJAX请求 + * @access public + * @param bool $pjax + * @return $this + */ + public function pjax($pjax = true) + { + return $this->option('pjax', $pjax); + } + + /** + * 检查是否为手机访问 + * @access public + * @param bool $mobile + * @return $this + */ + public function mobile($mobile = true) + { + return $this->option('mobile', $mobile); + } + + /** + * 当前路由到一个模板地址 当使用数组的时候可以传入模板变量 + * @access public + * @param bool|array $view + * @return $this + */ + public function view($view = true) + { + return $this->option('view', $view); + } + + /** + * 当前路由为重定向 + * @access public + * @param bool $redirect 是否为重定向 + * @return $this + */ + public function redirect($redirect = true) + { + return $this->option('redirect', $redirect); + } + + /** + * 设置路由完整匹配 + * @access public + * @param bool $match + * @return $this + */ + public function completeMatch($match = true) + { + return $this->option('complete_match', $match); + } + + /** + * 是否去除URL最后的斜线 + * @access public + * @param bool $remove + * @return $this + */ + public function removeSlash($remove = true) + { + return $this->option('remove_slash', $remove); + } + + /** + * 设置是否允许跨域 + * @access public + * @param bool $allow + * @param array $header + * @return $this + */ + public function allowCrossDomain($allow = true, $header = []) + { + if (!empty($header)) { + $this->header($header); + } + + if ($allow && $this->parent) { + $this->parent->addRuleItem($this, 'options'); + } + + return $this->option('cross_domain', $allow); + } + + /** + * 检查OPTIONS请求 + * @access public + * @param Request $request + * @return Dispatch|void + */ + protected function checkCrossDomain($request) + { + if (!empty($this->option['cross_domain'])) { + + $header = [ + 'Access-Control-Allow-Origin' => '*', + 'Access-Control-Allow-Methods' => 'GET, POST, PATCH, PUT, DELETE', + 'Access-Control-Allow-Headers' => 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With', + ]; + + if (!empty($this->option['header'])) { + $header = array_merge($header, $this->option['header']); + } + + $this->option['header'] = $header; + + if ($request->method(true) == 'OPTIONS') { + return new ResponseDispatch($request, $this, Response::create()->code(204)->header($header)); + } + } + } + + /** + * 设置路由规则全局有效 + * @access public + * @return $this + */ + public function crossDomainRule() + { + if ($this instanceof RuleGroup) { + $method = '*'; + } else { + $method = $this->method; + } + + $this->router->setCrossDomainRule($this, $method); + + return $this; + } + + /** + * 合并分组参数 + * @access public + * @return array + */ + public function mergeGroupOptions() + { + if (!$this->lockOption) { + $parentOption = $this->parent->getOption(); + // 合并分组参数 + foreach ($this->mergeOptions as $item) { + if (isset($parentOption[$item]) && isset($this->option[$item])) { + $this->option[$item] = array_merge($parentOption[$item], $this->option[$item]); + } + } + + $this->option = array_merge($parentOption, $this->option); + $this->lockOption = true; + } + + return $this->option; + } + + /** + * 解析匹配到的规则路由 + * @access public + * @param Request $request 请求对象 + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $url URL地址 + * @param array $option 路由参数 + * @param array $matches 匹配的变量 + * @return Dispatch + */ + public function parseRule($request, $rule, $route, $url, $option = [], $matches = []) + { + if (is_string($route) && isset($option['prefix'])) { + // 路由地址前缀 + $route = $option['prefix'] . $route; + } + + // 替换路由地址中的变量 + if (is_string($route) && !empty($matches)) { + foreach ($matches as $key => $val) { + if (false !== strpos($route, '<' . $key . '>')) { + $route = str_replace('<' . $key . '>', $val, $route); + } elseif (false !== strpos($route, ':' . $key)) { + $route = str_replace(':' . $key, $val, $route); + } + } + } + + // 解析额外参数 + $count = substr_count($rule, '/'); + $url = array_slice(explode('|', $url), $count + 1); + $this->parseUrlParams($request, implode('|', $url), $matches); + + $this->vars = $matches; + $this->option = $option; + $this->doAfter = true; + + // 发起路由调度 + return $this->dispatch($request, $route, $option); + } + + /** + * 检查路由前置行为 + * @access protected + * @param mixed $before 前置行为 + * @return mixed + */ + protected function checkBefore($before) + { + $hook = Container::get('hook'); + + foreach ((array) $before as $behavior) { + $result = $hook->exec($behavior); + + if (false === $result) { + return false; + } + } + } + + /** + * 发起路由调度 + * @access protected + * @param Request $request Request对象 + * @param mixed $route 路由地址 + * @param array $option 路由参数 + * @return Dispatch + */ + protected function dispatch($request, $route, $option) + { + if ($route instanceof \Closure) { + // 执行闭包 + $result = new CallbackDispatch($request, $this, $route); + } elseif ($route instanceof Response) { + $result = new ResponseDispatch($request, $this, $route); + } elseif (isset($option['view']) && false !== $option['view']) { + $result = new ViewDispatch($request, $this, $route, is_array($option['view']) ? $option['view'] : []); + } elseif (!empty($option['redirect']) || 0 === strpos($route, '/') || strpos($route, '://')) { + // 路由到重定向地址 + $result = new RedirectDispatch($request, $this, $route, [], isset($option['status']) ? $option['status'] : 301); + } elseif (false !== strpos($route, '\\')) { + // 路由到方法 + $result = $this->dispatchMethod($request, $route); + } elseif (0 === strpos($route, '@')) { + // 路由到控制器 + $result = $this->dispatchController($request, substr($route, 1)); + } else { + // 路由到模块/控制器/操作 + $result = $this->dispatchModule($request, $route); + } + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return CallbackDispatch + */ + protected function dispatchMethod($request, $route) + { + list($path, $var) = $this->parseUrlPath($route); + + $route = str_replace('/', '@', implode('/', $path)); + $method = strpos($route, '@') ? explode('@', $route) : $route; + + return new CallbackDispatch($request, $this, $method, $var); + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ControllerDispatch + */ + protected function dispatchController($request, $route) + { + list($route, $var) = $this->parseUrlPath($route); + + $result = new ControllerDispatch($request, $this, implode('/', $route), $var); + + $request->setAction(array_pop($route)); + $request->setController($route ? array_pop($route) : $this->getConfig('default_controller')); + $request->setModule($route ? array_pop($route) : $this->getConfig('default_module')); + + return $result; + } + + /** + * 解析URL地址为 模块/控制器/操作 + * @access protected + * @param Request $request Request对象 + * @param string $route 路由地址 + * @return ModuleDispatch + */ + protected function dispatchModule($request, $route) + { + list($path, $var) = $this->parseUrlPath($route); + + $action = array_pop($path); + $controller = !empty($path) ? array_pop($path) : null; + $module = $this->getConfig('app_multi_module') && !empty($path) ? array_pop($path) : null; + $method = $request->method(); + + if ($this->getConfig('use_action_prefix') && $this->router->getMethodPrefix($method)) { + $prefix = $this->router->getMethodPrefix($method); + // 操作方法前缀支持 + $action = 0 !== strpos($action, $prefix) ? $prefix . $action : $action; + } + + // 设置当前请求的路由变量 + $request->setRouteVars($var); + + // 路由到模块/控制器/操作 + return new ModuleDispatch($request, $this, [$module, $controller, $action], ['convert' => false]); + } + + /** + * 路由检查 + * @access protected + * @param array $option 路由参数 + * @param Request $request Request对象 + * @return bool + */ + protected function checkOption($option, Request $request) + { + // 请求类型检测 + if (!empty($option['method'])) { + if (is_string($option['method']) && false === stripos($option['method'], $request->method())) { + return false; + } + } + + // AJAX PJAX 请求检查 + foreach (['ajax', 'pjax', 'mobile'] as $item) { + if (isset($option[$item])) { + $call = 'is' . $item; + if ($option[$item] && !$request->$call() || !$option[$item] && $request->$call()) { + return false; + } + } + } + + // 伪静态后缀检测 + if ($request->url() != '/' && ((isset($option['ext']) && false === stripos('|' . $option['ext'] . '|', '|' . $request->ext() . '|')) + || (isset($option['deny_ext']) && false !== stripos('|' . $option['deny_ext'] . '|', '|' . $request->ext() . '|')))) { + return false; + } + + // 域名检查 + if ((isset($option['domain']) && !in_array($option['domain'], [$request->host(true), $request->subDomain()]))) { + return false; + } + + // HTTPS检查 + if ((isset($option['https']) && $option['https'] && !$request->isSsl()) + || (isset($option['https']) && !$option['https'] && $request->isSsl())) { + return false; + } + + // 请求参数检查 + if (isset($option['filter'])) { + foreach ($option['filter'] as $name => $value) { + if ($request->param($name, '', null) != $value) { + return false; + } + } + } + return true; + } + + /** + * 解析URL地址中的参数Request对象 + * @access protected + * @param Request $request + * @param string $rule 路由规则 + * @param array $var 变量 + * @return void + */ + protected function parseUrlParams($request, $url, &$var = []) + { + if ($url) { + if ($this->getConfig('url_param_type')) { + $var += explode('|', $url); + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, $url); + } + } + } + + /** + * 解析URL的pathinfo参数和变量 + * @access public + * @param string $url URL地址 + * @return array + */ + public function parseUrlPath($url) + { + // 分隔符替换 确保路由定义使用统一的分隔符 + $url = str_replace('|', '/', $url); + $url = trim($url, '/'); + $var = []; + + if (false !== strpos($url, '?')) { + // [模块/控制器/操作?]参数1=值1&参数2=值2... + $info = parse_url($url); + $path = explode('/', $info['path']); + parse_str($info['query'], $var); + } elseif (strpos($url, '/')) { + // [模块/控制器/操作] + $path = explode('/', $url); + } elseif (false !== strpos($url, '=')) { + // 参数1=值1&参数2=值2... + $path = []; + parse_str($url, $var); + } else { + $path = [$url]; + } + + return [$path, $var]; + } + + /** + * 生成路由的正则规则 + * @access protected + * @param string $rule 路由规则 + * @param array $match 匹配的变量 + * @param array $pattern 路由变量规则 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildRuleRegex($rule, $match, $pattern = [], $option = [], $completeMatch = false, $suffix = '') + { + foreach ($match as $name) { + $replace[] = $this->buildNameRegex($name, $pattern, $suffix); + } + + // 是否区分 / 地址访问 + if ('/' != $rule) { + if (!empty($option['remove_slash'])) { + $rule = rtrim($rule, '/'); + } elseif (substr($rule, -1) == '/') { + $rule = rtrim($rule, '/'); + $hasSlash = true; + } + } + + $regex = str_replace($match, $replace, $rule); + $regex = str_replace([')?/', ')/', ')?-', ')-', '\\\\/'], [')\/', ')\/', ')\-', ')\-', '\/'], $regex); + + if (isset($hasSlash)) { + $regex .= '\/'; + } + + return $regex . ($completeMatch ? '$' : ''); + } + + /** + * 生成路由变量的正则规则 + * @access protected + * @param string $name 路由变量 + * @param string $pattern 变量规则 + * @param string $suffix 路由正则变量后缀 + * @return string + */ + protected function buildNameRegex($name, $pattern, $suffix) + { + $optional = ''; + $slash = substr($name, 0, 1); + + if (in_array($slash, ['/', '-'])) { + $prefix = '\\' . $slash; + $name = substr($name, 1); + $slash = substr($name, 0, 1); + } else { + $prefix = ''; + } + + if ('<' != $slash) { + return $prefix . preg_quote($name, '/'); + } + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = '?'; + } elseif (strpos($name, '>')) { + $name = substr($name, 1, -1); + } + + if (isset($pattern[$name])) { + $nameRule = $pattern[$name]; + if (0 === strpos($nameRule, '/') && '/' == substr($nameRule, -1)) { + $nameRule = substr($nameRule, 1, -1); + } + } else { + $nameRule = $this->getConfig('default_route_pattern'); + } + + return '(' . $prefix . '(?<' . $name . $suffix . '>' . $nameRule . '))' . $optional; + } + + /** + * 分析路由规则中的变量 + * @access protected + * @param string $rule 路由规则 + * @return array + */ + protected function parseVar($rule) + { + // 提取路由规则中的变量 + $var = []; + + if (preg_match_all('/<\w+\??>/', $rule, $matches)) { + foreach ($matches[0] as $name) { + $optional = false; + + if (strpos($name, '?')) { + $name = substr($name, 1, -2); + $optional = true; + } else { + $name = substr($name, 1, -1); + } + + $var[$name] = $optional ? 2 : 1; + } + } + + return $var; + } + + /** + * 设置路由参数 + * @access public + * @param string $method 方法名 + * @param array $args 调用参数 + * @return $this + */ + public function __call($method, $args) + { + if (count($args) > 1) { + $args[0] = $args; + } + array_unshift($args, $method); + + return call_user_func_array([$this, 'option'], $args); + } + + public function __sleep() + { + return ['name', 'rule', 'route', 'method', 'vars', 'option', 'pattern', 'doAfter']; + } + + public function __wakeup() + { + $this->router = Container::get('route'); + } + + public function __debugInfo() + { + $data = get_object_vars($this); + unset($data['parent'], $data['router'], $data['route']); + + return $data; + } +} diff --git a/thinkphp/library/think/route/RuleGroup.php b/thinkphp/library/think/route/RuleGroup.php new file mode 100644 index 0000000..36be2f4 --- /dev/null +++ b/thinkphp/library/think/route/RuleGroup.php @@ -0,0 +1,605 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Exception; +use think\Request; +use think\Response; +use think\Route; +use think\route\dispatch\Response as ResponseDispatch; +use think\route\dispatch\Url as UrlDispatch; + +class RuleGroup extends Rule +{ + // 分组路由(包括子分组) + protected $rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + + // MISS路由 + protected $miss; + + // 自动路由 + protected $auto; + + // 完整名称 + protected $fullName; + + // 所在域名 + protected $domain; + + /** + * 架构函数 + * @access public + * @param Route $router 路由对象 + * @param RuleGroup $parent 上级对象 + * @param string $name 分组名称 + * @param mixed $rule 分组路由 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, RuleGroup $parent = null, $name = '', $rule = [], $option = [], $pattern = []) + { + $this->router = $router; + $this->parent = $parent; + $this->rule = $rule; + $this->name = trim($name, '/'); + $this->option = $option; + $this->pattern = $pattern; + + $this->setFullName(); + + if ($this->parent) { + $this->domain = $this->parent->getDomain(); + $this->parent->addRuleItem($this); + } + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this); + } + + if ($router->isTest()) { + $this->lazy(false); + } + } + + /** + * 设置分组的路由规则 + * @access public + * @return void + */ + protected function setFullName() + { + if (false !== strpos($this->name, ':')) { + $this->name = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $this->name); + } + + if ($this->parent && $this->parent->getFullName()) { + $this->fullName = $this->parent->getFullName() . ($this->name ? '/' . $this->name : ''); + } else { + $this->fullName = $this->name; + } + } + + /** + * 获取所属域名 + * @access public + * @return string + */ + public function getDomain() + { + return $this->domain; + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 跨域OPTIONS请求 + return $dispatch; + } + + // 检查分组有效性 + if (!$this->checkOption($this->option, $request) || !$this->checkUrl($url)) { + return false; + } + + // 检查前置行为 + if (isset($this->option['before'])) { + if (false === $this->checkBefore($this->option['before'])) { + return false; + } + unset($this->option['before']); + } + + // 解析分组路由 + if ($this instanceof Resource) { + $this->buildResourceRule(); + } elseif ($this->rule) { + if ($this->rule instanceof Response) { + return new ResponseDispatch($request, $this, $this->rule); + } + + $this->parseGroupRule($this->rule); + } + + // 获取当前路由规则 + $method = strtolower($request->method()); + $rules = $this->getMethodRules($method); + + if (count($rules) == 0) { + return false; + } + + if ($this->parent) { + // 合并分组参数 + $this->mergeGroupOptions(); + // 合并分组变量规则 + $this->pattern = array_merge($this->parent->getPattern(), $this->pattern); + } + + if (isset($this->option['complete_match'])) { + $completeMatch = $this->option['complete_match']; + } + + if (!empty($this->option['merge_rule_regex'])) { + // 合并路由正则规则进行路由匹配检查 + $result = $this->checkMergeRuleRegex($request, $rules, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + // 检查分组路由 + foreach ($rules as $key => $item) { + $result = $item->check($request, $url, $completeMatch); + + if (false !== $result) { + return $result; + } + } + + if ($this->auto) { + // 自动解析URL地址 + $result = new UrlDispatch($request, $this, $this->auto . '/' . $url, ['auto_search' => false]); + } elseif ($this->miss && in_array($this->miss->getMethod(), ['*', $method])) { + // 未匹配所有路由的路由规则处理 + $result = $this->miss->parseRule($request, '', $this->miss->getRoute(), $url, $this->miss->mergeGroupOptions()); + } else { + $result = false; + } + + return $result; + } + + /** + * 获取当前请求的路由规则(包括子分组、资源路由) + * @access protected + * @param string $method + * @return array + */ + protected function getMethodRules($method) + { + return array_merge($this->rules[$method], $this->rules['*']); + } + + /** + * 分组URL匹配检查 + * @access protected + * @param string $url + * @return bool + */ + protected function checkUrl($url) + { + if ($this->fullName) { + $pos = strpos($this->fullName, '<'); + + if (false !== $pos) { + $str = substr($this->fullName, 0, $pos); + } else { + $str = $this->fullName; + } + + if ($str && 0 !== stripos(str_replace('|', '/', $url), $str)) { + return false; + } + } + + return true; + } + + /** + * 延迟解析分组的路由规则 + * @access public + * @param bool $lazy 路由是否延迟解析 + * @return $this + */ + public function lazy($lazy = true) + { + if (!$lazy) { + $this->parseGroupRule($this->rule); + $this->rule = null; + } + + return $this; + } + + /** + * 解析分组和域名的路由规则及绑定 + * @access public + * @param mixed $rule 路由规则 + * @return void + */ + public function parseGroupRule($rule) + { + $origin = $this->router->getGroup(); + $this->router->setGroup($this); + + if ($rule instanceof \Closure) { + Container::getInstance()->invokeFunction($rule); + } elseif (is_array($rule)) { + $this->addRules($rule); + } elseif (is_string($rule) && $rule) { + $this->router->bind($rule, $this->domain); + } + + $this->router->setGroup($origin); + } + + /** + * 检测分组路由 + * @access public + * @param Request $request 请求对象 + * @param array $rules 路由规则 + * @param string $url 访问地址 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + protected function checkMergeRuleRegex($request, &$rules, $url, $completeMatch) + { + $depr = $this->router->config('pathinfo_depr'); + $url = $depr . str_replace('|', $depr, $url); + + foreach ($rules as $key => $item) { + if ($item instanceof RuleItem) { + $rule = $depr . str_replace('/', $depr, $item->getRule()); + if ($depr == $rule && $depr != $url) { + unset($rules[$key]); + continue; + } + + $complete = null !== $item->getOption('complete_match') ? $item->getOption('complete_match') : $completeMatch; + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$complete && 0 === strncasecmp($rule, $url, strlen($rule)))) { + return $item->checkRule($request, $url, []); + } + + unset($rules[$key]); + continue; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + unset($rules[$key]); + continue; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + unset($rules[$key]); + $pattern = array_merge($this->getPattern(), $item->getPattern()); + $option = array_merge($this->getOption(), $item->getOption()); + + $regex[$key] = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $complete, '_THINK_' . $key); + $items[$key] = $item; + } + } + } + + if (empty($regex)) { + return false; + } + + try { + $result = preg_match('/^(?:' . implode('|', $regex) . ')/u', $url, $match); + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + if ($result) { + $var = []; + foreach ($match as $key => $val) { + if (is_string($key) && '' !== $val) { + list($name, $pos) = explode('_THINK_', $key); + + $var[$name] = $val; + } + } + + if (!isset($pos)) { + foreach ($regex as $key => $item) { + if (0 === strpos(str_replace(['\/', '\-', '\\' . $depr], ['/', '-', $depr], $item), $match[0])) { + $pos = $key; + break; + } + } + } + + $rule = $items[$pos]->getRule(); + $array = $this->router->getRule($rule); + + foreach ($array as $item) { + if (in_array($item->getMethod(), ['*', strtolower($request->method())])) { + $result = $item->checkRule($request, $url, $var); + + if (false !== $result) { + return $result; + } + } + } + } + + return false; + } + + /** + * 获取分组的MISS路由 + * @access public + * @return RuleItem|null + */ + public function getMissRule() + { + return $this->miss; + } + + /** + * 获取分组的自动路由 + * @access public + * @return string + */ + public function getAutoRule() + { + return $this->auto; + } + + /** + * 注册自动路由 + * @access public + * @param string $route 路由规则 + * @return void + */ + public function addAutoRule($route) + { + $this->auto = $route; + } + + /** + * 注册MISS路由 + * @access public + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @return RuleItem + */ + public function addMissRule($route, $method = '*', $option = []) + { + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, null, '', $route, strtolower($method), $option); + + $this->miss = $ruleItem; + + return $ruleItem; + } + + /** + * 添加分组下的路由规则或者子分组 + * @access public + * @param string $rule 路由规则 + * @param string $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return $this + */ + public function addRule($rule, $route, $method = '*', $option = [], $pattern = []) + { + // 读取路由标识 + if (is_array($rule)) { + $name = $rule[0]; + $rule = $rule[1]; + } elseif (is_string($route)) { + $name = $route; + } else { + $name = null; + } + + $method = strtolower($method); + + if ('/' === $rule || '' === $rule) { + // 首页自动完整匹配 + $rule .= '$'; + } + + // 创建路由规则实例 + $ruleItem = new RuleItem($this->router, $this, $name, $rule, $route, $method, $option, $pattern); + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($ruleItem, $method); + } + + $this->addRuleItem($ruleItem, $method); + + return $ruleItem; + } + + /** + * 批量注册路由规则 + * @access public + * @param array $rules 路由规则 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + * @return void + */ + public function addRules($rules, $method = '*', $option = [], $pattern = []) + { + foreach ($rules as $key => $val) { + if (is_numeric($key)) { + $key = array_shift($val); + } + + if (is_array($val)) { + $route = array_shift($val); + $option = $val ? array_shift($val) : []; + $pattern = $val ? array_shift($val) : []; + } else { + $route = $val; + } + + $this->addRule($key, $route, $method, $option, $pattern); + } + } + + public function addRuleItem($rule, $method = '*') + { + if (strpos($method, '|')) { + $rule->method($method); + $method = '*'; + } + + $this->rules[$method][] = $rule; + + return $this; + } + + /** + * 设置分组的路由前缀 + * @access public + * @param string $prefix + * @return $this + */ + public function prefix($prefix) + { + if ($this->parent && $this->parent->getOption('prefix')) { + $prefix = $this->parent->getOption('prefix') . $prefix; + } + + return $this->option('prefix', $prefix); + } + + /** + * 设置资源允许 + * @access public + * @param array $only + * @return $this + */ + public function only($only) + { + return $this->option('only', $only); + } + + /** + * 设置资源排除 + * @access public + * @param array $except + * @return $this + */ + public function except($except) + { + return $this->option('except', $except); + } + + /** + * 设置资源路由的变量 + * @access public + * @param array $vars + * @return $this + */ + public function vars($vars) + { + return $this->option('var', $vars); + } + + /** + * 合并分组的路由规则正则 + * @access public + * @param bool $merge + * @return $this + */ + public function mergeRuleRegex($merge = true) + { + return $this->option('merge_rule_regex', $merge); + } + + /** + * 获取完整分组Name + * @access public + * @return string + */ + public function getFullName() + { + return $this->fullName; + } + + /** + * 获取分组的路由规则 + * @access public + * @param string $method + * @return array + */ + public function getRules($method = '') + { + if ('' === $method) { + return $this->rules; + } + + return isset($this->rules[strtolower($method)]) ? $this->rules[strtolower($method)] : []; + } + + /** + * 清空分组下的路由规则 + * @access public + * @return void + */ + public function clear() + { + $this->rules = [ + '*' => [], + 'get' => [], + 'post' => [], + 'put' => [], + 'patch' => [], + 'delete' => [], + 'head' => [], + 'options' => [], + ]; + } +} diff --git a/thinkphp/library/think/route/RuleItem.php b/thinkphp/library/think/route/RuleItem.php new file mode 100644 index 0000000..e4bddd9 --- /dev/null +++ b/thinkphp/library/think/route/RuleItem.php @@ -0,0 +1,288 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +use think\Container; +use think\Exception; +use think\Route; + +class RuleItem extends Rule +{ + protected $hasSetRule; + + /** + * 架构函数 + * @access public + * @param Route $router 路由实例 + * @param RuleGroup $parent 上级对象 + * @param string $name 路由标识 + * @param string|array $rule 路由规则 + * @param string|\Closure $route 路由地址 + * @param string $method 请求类型 + * @param array $option 路由参数 + * @param array $pattern 变量规则 + */ + public function __construct(Route $router, RuleGroup $parent, $name, $rule, $route, $method = '*', $option = [], $pattern = []) + { + $this->router = $router; + $this->parent = $parent; + $this->name = $name; + $this->route = $route; + $this->method = $method; + $this->option = $option; + $this->pattern = $pattern; + + $this->setRule($rule); + + if (!empty($option['cross_domain'])) { + $this->router->setCrossDomainRule($this, $method); + } + } + + /** + * 路由规则预处理 + * @access public + * @param string $rule 路由规则 + * @return void + */ + public function setRule($rule) + { + if ('$' == substr($rule, -1, 1)) { + // 是否完整匹配 + $rule = substr($rule, 0, -1); + + $this->option['complete_match'] = true; + } + + $rule = '/' != $rule ? ltrim($rule, '/') : ''; + + if ($this->parent && $prefix = $this->parent->getFullName()) { + $rule = $prefix . ($rule ? '/' . ltrim($rule, '/') : ''); + } + + if (false !== strpos($rule, ':')) { + $this->rule = preg_replace(['/\[\:(\w+)\]/', '/\:(\w+)/'], ['<\1?>', '<\1>'], $rule); + } else { + $this->rule = $rule; + } + + // 生成路由标识的快捷访问 + $this->setRuleName(); + } + + /** + * 检查后缀 + * @access public + * @param string $ext + * @return $this + */ + public function ext($ext = '') + { + $this->option('ext', $ext); + $this->setRuleName(true); + + return $this; + } + + /** + * 设置别名 + * @access public + * @param string $name + * @return $this + */ + public function name($name) + { + $this->name = $name; + $this->setRuleName(true); + + return $this; + } + + /** + * 设置路由标识 用于URL反解生成 + * @access protected + * @param bool $first 是否插入开头 + * @return void + */ + protected function setRuleName($first = false) + { + if ($this->name) { + $vars = $this->parseVar($this->rule); + $name = strtolower($this->name); + + if (isset($this->option['ext'])) { + $suffix = $this->option['ext']; + } elseif ($this->parent->getOption('ext')) { + $suffix = $this->parent->getOption('ext'); + } else { + $suffix = null; + } + + $value = [$this->rule, $vars, $this->parent->getDomain(), $suffix, $this->method]; + + Container::get('rule_name')->set($name, $value, $first); + } + + if (!$this->hasSetRule) { + Container::get('rule_name')->setRule($this->rule, $this); + $this->hasSetRule = true; + } + } + + /** + * 检测路由 + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $match 匹配路由变量 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function checkRule($request, $url, $match = null, $completeMatch = false) + { + if ($dispatch = $this->checkCrossDomain($request)) { + // 允许跨域 + return $dispatch; + } + + // 检查参数有效性 + if (!$this->checkOption($this->option, $request)) { + return false; + } + + // 合并分组参数 + $option = $this->mergeGroupOptions(); + + $url = $this->urlSuffixCheck($request, $url, $option); + + if (is_null($match)) { + $match = $this->match($url, $option, $completeMatch); + } + + if (false !== $match) { + // 检查前置行为 + if (isset($option['before']) && false === $this->checkBefore($option['before'])) { + return false; + } + + return $this->parseRule($request, $this->rule, $this->route, $url, $option, $match); + } + + return false; + } + + /** + * 检测路由(含路由匹配) + * @access public + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param string $depr 路径分隔符 + * @param bool $completeMatch 路由是否完全匹配 + * @return Dispatch|false + */ + public function check($request, $url, $completeMatch = false) + { + return $this->checkRule($request, $url, null, $completeMatch); + } + + /** + * URL后缀及Slash检查 + * @access protected + * @param Request $request 请求对象 + * @param string $url 访问地址 + * @param array $option 路由参数 + * @return string + */ + protected function urlSuffixCheck($request, $url, $option = []) + { + // 是否区分 / 地址访问 + if (!empty($option['remove_slash']) && '/' != $this->rule) { + $this->rule = rtrim($this->rule, '/'); + $url = rtrim($url, '|'); + } + + if (isset($option['ext'])) { + // 路由ext参数 优先于系统配置的URL伪静态后缀参数 + $url = preg_replace('/\.(' . $request->ext() . ')$/i', '', $url); + } + + return $url; + } + + /** + * 检测URL和规则路由是否匹配 + * @access private + * @param string $url URL地址 + * @param array $option 路由参数 + * @param bool $completeMatch 路由是否完全匹配 + * @return array|false + */ + private function match($url, $option, $completeMatch) + { + if (isset($option['complete_match'])) { + $completeMatch = $option['complete_match']; + } + + $pattern = array_merge($this->parent->getPattern(), $this->pattern); + $depr = $this->router->config('pathinfo_depr'); + + // 检查完整规则定义 + if (isset($pattern['__url__']) && !preg_match(0 === strpos($pattern['__url__'], '/') ? $pattern['__url__'] : '/^' . $pattern['__url__'] . '/', str_replace('|', $depr, $url))) { + return false; + } + + $var = []; + $url = $depr . str_replace('|', $depr, $url); + $rule = $depr . str_replace('/', $depr, $this->rule); + + if ($depr == $rule && $depr != $url) { + return false; + } + + if (false === strpos($rule, '<')) { + if (0 === strcasecmp($rule, $url) || (!$completeMatch && 0 === strncasecmp($rule . $depr, $url . $depr, strlen($rule . $depr)))) { + return $var; + } + return false; + } + + $slash = preg_quote('/-' . $depr, '/'); + + if ($matchRule = preg_split('/[' . $slash . ']?<\w+\??>/', $rule, 2)) { + if ($matchRule[0] && 0 !== strncasecmp($rule, $url, strlen($matchRule[0]))) { + return false; + } + } + + if (preg_match_all('/[' . $slash . ']??/', $rule, $matches)) { + $regex = $this->buildRuleRegex($rule, $matches[0], $pattern, $option, $completeMatch); + + try { + if (!preg_match('/^' . $regex . ($completeMatch ? '$' : '') . '/u', $url, $match)) { + return false; + } + } catch (\Exception $e) { + throw new Exception('route pattern error'); + } + + foreach ($match as $key => $val) { + if (is_string($key)) { + $var[$key] = $val; + } + } + } + + // 成功匹配后返回URL中的动态变量数组 + return $var; + } + +} diff --git a/thinkphp/library/think/route/RuleName.php b/thinkphp/library/think/route/RuleName.php new file mode 100644 index 0000000..202fb0e --- /dev/null +++ b/thinkphp/library/think/route/RuleName.php @@ -0,0 +1,147 @@ + +// +---------------------------------------------------------------------- + +namespace think\route; + +class RuleName +{ + protected $item = []; + protected $rule = []; + + /** + * 注册路由标识 + * @access public + * @param string $name 路由标识 + * @param array $value 路由规则 + * @param bool $first 是否置顶 + * @return void + */ + public function set($name, $value, $first = false) + { + if ($first && isset($this->item[$name])) { + array_unshift($this->item[$name], $value); + } else { + $this->item[$name][] = $value; + } + } + + /** + * 注册路由规则 + * @access public + * @param string $rule 路由规则 + * @param RuleItem $route 路由 + * @return void + */ + public function setRule($rule, $route) + { + $this->rule[$route->getDomain()][$rule][$route->getMethod()] = $route; + } + + /** + * 根据路由规则获取路由对象(列表) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @return array + */ + public function getRule($rule, $domain = null) + { + return isset($this->rule[$domain][$rule]) ? $this->rule[$domain][$rule] : []; + } + + /** + * 获取全部路由列表 + * @access public + * @param string $domain 域名 + * @return array + */ + public function getRuleList($domain = null) + { + $list = []; + + foreach ($this->rule as $ruleDomain => $rules) { + foreach ($rules as $rule => $items) { + foreach ($items as $item) { + $val['domain'] = $ruleDomain; + + foreach (['method', 'rule', 'name', 'route', 'pattern', 'option'] as $param) { + $call = 'get' . $param; + $val[$param] = $item->$call(); + } + + $list[$ruleDomain][] = $val; + } + } + } + + if ($domain) { + return isset($list[$domain]) ? $list[$domain] : []; + } + + return $list; + } + + /** + * 导入路由标识 + * @access public + * @param array $name 路由标识 + * @return void + */ + public function import($item) + { + $this->item = $item; + } + + /** + * 根据路由标识获取路由信息(用于URL生成) + * @access public + * @param string $name 路由标识 + * @param string $domain 域名 + * @return array|null + */ + public function get($name = null, $domain = null, $method = '*') + { + if (is_null($name)) { + return $this->item; + } + + $name = strtolower($name); + $method = strtolower($method); + + if (isset($this->item[$name])) { + if (is_null($domain)) { + $result = $this->item[$name]; + } else { + $result = []; + foreach ($this->item[$name] as $item) { + if ($item[2] == $domain && ('*' == $item[4] || $method == $item[4])) { + $result[] = $item; + } + } + } + } else { + $result = null; + } + + return $result; + } + + /** + * 清空路由规则 + * @access public + * @return void + */ + public function clear() + { + $this->item = []; + $this->rule = []; + } +} diff --git a/thinkphp/library/think/route/dispatch/Callback.php b/thinkphp/library/think/route/dispatch/Callback.php new file mode 100644 index 0000000..ca76fc9 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Callback.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Callback extends Dispatch +{ + public function exec() + { + // 执行回调方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->invoke($this->dispatch, $vars); + } + +} diff --git a/thinkphp/library/think/route/dispatch/Controller.php b/thinkphp/library/think/route/dispatch/Controller.php new file mode 100644 index 0000000..1de8299 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Controller.php @@ -0,0 +1,30 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Controller extends Dispatch +{ + public function exec() + { + // 执行控制器的操作方法 + $vars = array_merge($this->request->param(), $this->param); + + return $this->app->action( + $this->dispatch, $vars, + $this->rule->getConfig('url_controller_layer'), + $this->rule->getConfig('controller_suffix') + ); + } + +} diff --git a/thinkphp/library/think/route/dispatch/Module.php b/thinkphp/library/think/route/dispatch/Module.php new file mode 100644 index 0000000..dc1974c --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Module.php @@ -0,0 +1,147 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use ReflectionMethod; +use think\Controller; +use think\exception\ClassNotFoundException; +use think\exception\HttpException; +use think\Loader; +use think\Request; +use think\route\Dispatch; + +class Module extends Dispatch +{ + protected $controller; + protected $actionName; + + public function init() + { + parent::init(); + + $result = $this->dispatch; + + if (is_string($result)) { + $result = explode('/', $result); + } + + if ($this->rule->getConfig('app_multi_module')) { + // 多模块部署 + $module = strip_tags(strtolower($result[0] ?: $this->rule->getConfig('default_module'))); + $bind = $this->rule->getRouter()->getBind(); + $available = false; + + if ($bind && preg_match('/^[a-z]/is', $bind)) { + // 绑定模块 + list($bindModule) = explode('/', $bind); + if (empty($result[0])) { + $module = $bindModule; + } + $available = true; + } elseif (!in_array($module, $this->rule->getConfig('deny_module_list')) && is_dir($this->app->getAppPath() . $module)) { + $available = true; + } elseif ($this->rule->getConfig('empty_module')) { + $module = $this->rule->getConfig('empty_module'); + $available = true; + } + + // 模块初始化 + if ($module && $available) { + // 初始化模块 + $this->request->setModule($module); + $this->app->init($module); + } else { + throw new HttpException(404, 'module not exists:' . $module); + } + } + + // 是否自动转换控制器和操作名 + $convert = is_bool($this->convert) ? $this->convert : $this->rule->getConfig('url_convert'); + // 获取控制器名 + $controller = strip_tags($result[1] ?: $this->rule->getConfig('default_controller')); + + if (!preg_match('/^[A-Za-z](\w|\.)*$/', $controller)) { + throw new HttpException(404, 'controller not exists:' . $controller); + } + + $this->controller = $convert ? strtolower($controller) : $controller; + + // 获取操作名 + $this->actionName = strip_tags($result[2] ?: $this->rule->getConfig('default_action')); + + // 设置当前请求的控制器、操作 + $this->request + ->setController(Loader::parseName($this->controller, 1)) + ->setAction($this->actionName); + + return $this; + } + + public function exec() + { + // 监听module_init + $this->app['hook']->listen('module_init'); + + try { + // 实例化控制器 + $instance = $this->app->controller($this->controller, + $this->rule->getConfig('url_controller_layer'), + $this->rule->getConfig('controller_suffix'), + $this->rule->getConfig('empty_controller')); + + if ($instance instanceof Controller) { + $instance->registerMiddleware(); + } + } catch (ClassNotFoundException $e) { + throw new HttpException(404, 'controller not exists:' . $e->getClass()); + } + + $this->app['middleware']->controller(function (Request $request, $next) use ($instance) { + // 获取当前操作名 + $action = $this->actionName . $this->rule->getConfig('action_suffix'); + + if (is_callable([$instance, $action])) { + // 执行操作方法 + $call = [$instance, $action]; + + // 严格获取当前操作方法名 + $reflect = new ReflectionMethod($instance, $action); + $methodName = $reflect->getName(); + $suffix = $this->rule->getConfig('action_suffix'); + $actionName = $suffix ? substr($methodName, 0, -strlen($suffix)) : $methodName; + $this->request->setAction($actionName); + + // 自动获取请求变量 + $vars = $this->rule->getConfig('url_param_type') + ? $this->request->route() + : $this->request->param(); + $vars = array_merge($vars, $this->param); + } elseif (is_callable([$instance, '_empty'])) { + // 空操作 + $call = [$instance, '_empty']; + $vars = [$this->actionName]; + $reflect = new ReflectionMethod($instance, '_empty'); + } else { + // 操作不存在 + throw new HttpException(404, 'method not exists:' . get_class($instance) . '->' . $action . '()'); + } + + $this->app['hook']->listen('action_begin', $call); + + $data = $this->app->invokeReflectMethod($instance, $reflect, $vars); + + return $this->autoResponse($data); + }); + + return $this->app['middleware']->dispatch($this->request, 'controller'); + } +} diff --git a/thinkphp/library/think/route/dispatch/Redirect.php b/thinkphp/library/think/route/dispatch/Redirect.php new file mode 100644 index 0000000..fae2c9a --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Redirect.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Response; +use think\route\Dispatch; + +class Redirect extends Dispatch +{ + public function exec() + { + return Response::create($this->dispatch, 'redirect')->code($this->code); + } +} diff --git a/thinkphp/library/think/route/dispatch/Response.php b/thinkphp/library/think/route/dispatch/Response.php new file mode 100644 index 0000000..66f4e5a --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Response.php @@ -0,0 +1,23 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\route\Dispatch; + +class Response extends Dispatch +{ + public function exec() + { + return $this->dispatch; + } + +} diff --git a/thinkphp/library/think/route/dispatch/Url.php b/thinkphp/library/think/route/dispatch/Url.php new file mode 100644 index 0000000..95ee9e5 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/Url.php @@ -0,0 +1,165 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\exception\HttpException; +use think\Loader; +use think\route\Dispatch; + +class Url extends Dispatch +{ + public function init() + { + // 解析默认的URL规则 + $result = $this->parseUrl($this->dispatch); + + return (new Module($this->request, $this->rule, $result))->init(); + } + + public function exec() + {} + + /** + * 解析URL地址 + * @access protected + * @param string $url URL + * @return array + */ + protected function parseUrl($url) + { + $depr = $this->rule->getConfig('pathinfo_depr'); + $bind = $this->rule->getRouter()->getBind(); + + if (!empty($bind) && preg_match('/^[a-z]/is', $bind)) { + $bind = str_replace('/', $depr, $bind); + // 如果有模块/控制器绑定 + $url = $bind . ('.' != substr($bind, -1) ? $depr : '') . ltrim($url, $depr); + } + + list($path, $var) = $this->rule->parseUrlPath($url); + if (empty($path)) { + return [null, null, null]; + } + + // 解析模块 + $module = $this->rule->getConfig('app_multi_module') ? array_shift($path) : null; + + if ($this->param['auto_search']) { + $controller = $this->autoFindController($module, $path); + } else { + // 解析控制器 + $controller = !empty($path) ? array_shift($path) : null; + } + + // 解析操作 + $action = !empty($path) ? array_shift($path) : null; + + // 解析额外参数 + if ($path) { + if ($this->rule->getConfig('url_param_type')) { + $var += $path; + } else { + preg_replace_callback('/(\w+)\|([^\|]+)/', function ($match) use (&$var) { + $var[$match[1]] = strip_tags($match[2]); + }, implode('|', $path)); + } + } + + $panDomain = $this->request->panDomain(); + + if ($panDomain && $key = array_search('*', $var)) { + // 泛域名赋值 + $var[$key] = $panDomain; + } + + // 设置当前请求的参数 + $this->request->setRouteVars($var); + + // 封装路由 + $route = [$module, $controller, $action]; + + if ($this->hasDefinedRoute($route, $bind)) { + throw new HttpException(404, 'invalid request:' . str_replace('|', $depr, $url)); + } + + return $route; + } + + /** + * 检查URL是否已经定义过路由 + * @access protected + * @param string $route 路由信息 + * @param string $bind 绑定信息 + * @return bool + */ + protected function hasDefinedRoute($route, $bind) + { + list($module, $controller, $action) = $route; + + // 检查地址是否被定义过路由 + $name = strtolower($module . '/' . Loader::parseName($controller, 1) . '/' . $action); + + $name2 = ''; + + if (empty($module) || $module == $bind) { + $name2 = strtolower(Loader::parseName($controller, 1) . '/' . $action); + } + + $host = $this->request->host(true); + + $method = $this->request->method(); + + if ($this->rule->getRouter()->getName($name, $host, $method) || $this->rule->getRouter()->getName($name2, $host, $method)) { + return true; + } + + return false; + } + + /** + * 自动定位控制器类 + * @access protected + * @param string $module 模块名 + * @param array $path URL + * @return string + */ + protected function autoFindController($module, &$path) + { + $dir = $this->app->getAppPath() . ($module ? $module . '/' : '') . $this->rule->getConfig('url_controller_layer'); + $suffix = $this->app->getSuffix() || $this->rule->getConfig('controller_suffix') ? ucfirst($this->rule->getConfig('url_controller_layer')) : ''; + + $item = []; + $find = false; + + foreach ($path as $val) { + $item[] = $val; + $file = $dir . '/' . str_replace('.', '/', $val) . $suffix . '.php'; + $file = pathinfo($file, PATHINFO_DIRNAME) . '/' . Loader::parseName(pathinfo($file, PATHINFO_FILENAME), 1) . '.php'; + if (is_file($file)) { + $find = true; + break; + } else { + $dir .= '/' . Loader::parseName($val); + } + } + + if ($find) { + $controller = implode('.', $item); + $path = array_slice($path, count($item)); + } else { + $controller = array_shift($path); + } + + return $controller; + } + +} diff --git a/thinkphp/library/think/route/dispatch/View.php b/thinkphp/library/think/route/dispatch/View.php new file mode 100644 index 0000000..ea3ef11 --- /dev/null +++ b/thinkphp/library/think/route/dispatch/View.php @@ -0,0 +1,26 @@ + +// +---------------------------------------------------------------------- + +namespace think\route\dispatch; + +use think\Response; +use think\route\Dispatch; + +class View extends Dispatch +{ + public function exec() + { + // 渲染模板输出 + $vars = array_merge($this->request->param(), $this->param); + + return Response::create($this->dispatch, 'view')->assign($vars); + } +} diff --git a/thinkphp/library/think/session/driver/Memcache.php b/thinkphp/library/think/session/driver/Memcache.php new file mode 100644 index 0000000..40d7bb8 --- /dev/null +++ b/thinkphp/library/think/session/driver/Memcache.php @@ -0,0 +1,124 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandlerInterface; +use think\Exception; + +class Memcache implements SessionHandlerInterface +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'persistent' => true, // 长连接 + 'session_name' => '', // memcache key前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcache')) { + throw new Exception('not support:memcache'); + } + + $this->handler = new \Memcache; + + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + foreach ((array) $hosts as $i => $host) { + $port = isset($ports[$i]) ? $ports[$i] : $ports[0]; + $this->config['timeout'] > 0 ? + $this->handler->addServer($host, $port, $this->config['persistent'], 1, $this->config['timeout']) : + $this->handler->addServer($host, $port, $this->config['persistent'], 1); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Memcached.php b/thinkphp/library/think/session/driver/Memcached.php new file mode 100644 index 0000000..074b2ff --- /dev/null +++ b/thinkphp/library/think/session/driver/Memcached.php @@ -0,0 +1,135 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandlerInterface; +use think\Exception; + +class Memcached implements SessionHandlerInterface +{ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // memcache主机 + 'port' => 11211, // memcache端口 + 'expire' => 3600, // session有效期 + 'timeout' => 0, // 连接超时时间(单位:毫秒) + 'session_name' => '', // memcache key前缀 + 'username' => '', //账号 + 'password' => '', //密码 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + */ + public function open($savePath, $sessName) + { + // 检测php环境 + if (!extension_loaded('memcached')) { + throw new Exception('not support:memcached'); + } + + $this->handler = new \Memcached; + + // 设置连接超时时间(单位:毫秒) + if ($this->config['timeout'] > 0) { + $this->handler->setOption(\Memcached::OPT_CONNECT_TIMEOUT, $this->config['timeout']); + } + + // 支持集群 + $hosts = explode(',', $this->config['host']); + $ports = explode(',', $this->config['port']); + + if (empty($ports[0])) { + $ports[0] = 11211; + } + + // 建立连接 + $servers = []; + foreach ((array) $hosts as $i => $host) { + $servers[] = [$host, (isset($ports[$i]) ? $ports[$i] : $ports[0]), 1]; + } + + $this->handler->addServers($servers); + + if ('' != $this->config['username']) { + $this->handler->setOption(\Memcached::OPT_BINARY_PROTOCOL, true); + $this->handler->setSaslAuthData($this->config['username'], $this->config['password']); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->quit(); + $this->handler = null; + + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']); + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID); + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return true + */ + public function gc($sessMaxLifeTime) + { + return true; + } +} diff --git a/thinkphp/library/think/session/driver/Redis.php b/thinkphp/library/think/session/driver/Redis.php new file mode 100644 index 0000000..646700b --- /dev/null +++ b/thinkphp/library/think/session/driver/Redis.php @@ -0,0 +1,179 @@ + +// +---------------------------------------------------------------------- + +namespace think\session\driver; + +use SessionHandlerInterface; +use think\Exception; + +class Redis implements SessionHandlerInterface +{ + /** @var \Redis */ + protected $handler = null; + protected $config = [ + 'host' => '127.0.0.1', // redis主机 + 'port' => 6379, // redis端口 + 'password' => '', // 密码 + 'select' => 0, // 操作库 + 'expire' => 3600, // 有效期(秒) + 'timeout' => 0, // 超时时间(秒) + 'persistent' => true, // 是否长连接 + 'session_name' => '', // sessionkey前缀 + ]; + + public function __construct($config = []) + { + $this->config = array_merge($this->config, $config); + } + + /** + * 打开Session + * @access public + * @param string $savePath + * @param mixed $sessName + * @return bool + * @throws Exception + */ + public function open($savePath, $sessName) + { + if (extension_loaded('redis')) { + $this->handler = new \Redis; + + // 建立连接 + $func = $this->config['persistent'] ? 'pconnect' : 'connect'; + $this->handler->$func($this->config['host'], $this->config['port'], $this->config['timeout']); + + if ('' != $this->config['password']) { + $this->handler->auth($this->config['password']); + } + + if (0 != $this->config['select']) { + $this->handler->select($this->config['select']); + } + } elseif (class_exists('\Predis\Client')) { + $params = []; + foreach ($this->config as $key => $val) { + if (in_array($key, ['aggregate', 'cluster', 'connections', 'exceptions', 'prefix', 'profile', 'replication'])) { + $params[$key] = $val; + unset($this->config[$key]); + } + } + $this->handler = new \Predis\Client($this->config, $params); + } else { + throw new \BadFunctionCallException('not support: redis'); + } + + return true; + } + + /** + * 关闭Session + * @access public + */ + public function close() + { + $this->gc(ini_get('session.gc_maxlifetime')); + $this->handler->close(); + $this->handler = null; + + return true; + } + + /** + * 读取Session + * @access public + * @param string $sessID + * @return string + */ + public function read($sessID) + { + return (string) $this->handler->get($this->config['session_name'] . $sessID); + } + + /** + * 写入Session + * @access public + * @param string $sessID + * @param string $sessData + * @return bool + */ + public function write($sessID, $sessData) + { + if ($this->config['expire'] > 0) { + $result = $this->handler->setex($this->config['session_name'] . $sessID, $this->config['expire'], $sessData); + } else { + $result = $this->handler->set($this->config['session_name'] . $sessID, $sessData); + } + + return $result ? true : false; + } + + /** + * 删除Session + * @access public + * @param string $sessID + * @return bool + */ + public function destroy($sessID) + { + return $this->handler->delete($this->config['session_name'] . $sessID) > 0; + } + + /** + * Session 垃圾回收 + * @access public + * @param string $sessMaxLifeTime + * @return bool + */ + public function gc($sessMaxLifeTime) + { + return true; + } + + /** + * Redis Session 驱动的加锁机制 + * @access public + * @param string $sessID 用于加锁的sessID + * @param integer $timeout 默认过期时间 + * @return bool + */ + public function lock($sessID, $timeout = 10) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $lockKey = 'LOCK_PREFIX_' . $sessID; + // 使用setnx操作加锁 + $isLock = $this->handler->setnx($lockKey, 1); + if ($isLock) { + // 设置过期时间,防止死任务的出现 + $this->handler->expire($lockKey, $timeout); + return true; + } + + return false; + } + + /** + * Redis Session 驱动的解锁机制 + * @access public + * @param string $sessID 用于解锁的sessID + */ + public function unlock($sessID) + { + if (null == $this->handler) { + $this->open('', ''); + } + + $this->handler->del('LOCK_PREFIX_' . $sessID); + } +} diff --git a/thinkphp/library/think/template/TagLib.php b/thinkphp/library/think/template/TagLib.php new file mode 100644 index 0000000..bbbb2c0 --- /dev/null +++ b/thinkphp/library/think/template/TagLib.php @@ -0,0 +1,351 @@ + +// +---------------------------------------------------------------------- + +namespace think\template; + +use think\Exception; + +/** + * ThinkPHP标签库TagLib解析基类 + * @category Think + * @package Think + * @subpackage Template + * @author liu21st + */ +class TagLib +{ + + /** + * 标签库定义XML文件 + * @var string + * @access protected + */ + protected $xml = ''; + protected $tags = []; // 标签定义 + /** + * 标签库名称 + * @var string + * @access protected + */ + protected $tagLib = ''; + + /** + * 标签库标签列表 + * @var array + * @access protected + */ + protected $tagList = []; + + /** + * 标签库分析数组 + * @var array + * @access protected + */ + protected $parse = []; + + /** + * 标签库是否有效 + * @var bool + * @access protected + */ + protected $valid = false; + + /** + * 当前模板对象 + * @var object + * @access protected + */ + protected $tpl; + + protected $comparison = [' nheq ' => ' !== ', ' heq ' => ' === ', ' neq ' => ' != ', ' eq ' => ' == ', ' egt ' => ' >= ', ' gt ' => ' > ', ' elt ' => ' <= ', ' lt ' => ' < ']; + + /** + * 架构函数 + * @access public + * @param \stdClass $template 模板引擎对象 + */ + public function __construct($template) + { + $this->tpl = $template; + } + + /** + * 按签标库替换页面中的标签 + * @access public + * @param string $content 模板内容 + * @param string $lib 标签库名 + * @return void + */ + public function parseTag(&$content, $lib = '') + { + $tags = []; + $lib = $lib ? strtolower($lib) . ':' : ''; + + foreach ($this->tags as $name => $val) { + $close = !isset($val['close']) || $val['close'] ? 1 : 0; + $tags[$close][$lib . $name] = $name; + if (isset($val['alias'])) { + // 别名设置 + $array = (array) $val['alias']; + foreach (explode(',', $array[0]) as $v) { + $tags[$close][$lib . $v] = $name; + } + } + } + + // 闭合标签 + if (!empty($tags[1])) { + $nodes = []; + $regex = $this->getRegex(array_keys($tags[1]), 1); + if (preg_match_all($regex, $content, $matches, PREG_SET_ORDER | PREG_OFFSET_CAPTURE)) { + $right = []; + foreach ($matches as $match) { + if ('' == $match[1][0]) { + $name = strtolower($match[2][0]); + // 如果有没闭合的标签头则取出最后一个 + if (!empty($right[$name])) { + // $match[0][1]为标签结束符在模板中的位置 + $nodes[$match[0][1]] = [ + 'name' => $name, + 'begin' => array_pop($right[$name]), // 标签开始符 + 'end' => $match[0], // 标签结束符 + ]; + } + } else { + // 标签头压入栈 + $right[strtolower($match[1][0])][] = $match[0]; + } + } + unset($right, $matches); + // 按标签在模板中的位置从后向前排序 + krsort($nodes); + } + + $break = ''; + if ($nodes) { + $beginArray = []; + // 标签替换 从后向前 + foreach ($nodes as $pos => $node) { + // 对应的标签名 + $name = $tags[1][$node['name']]; + $alias = $lib . $name != $node['name'] ? ($lib ? strstr($node['name'], $lib) : $node['name']) : ''; + + // 解析标签属性 + $attrs = $this->parseAttr($node['begin'][0], $name, $alias); + $method = 'tag' . $name; + + // 读取标签库中对应的标签内容 replace[0]用来替换标签头,replace[1]用来替换标签尾 + $replace = explode($break, $this->$method($attrs, $break)); + + if (count($replace) > 1) { + while ($beginArray) { + $begin = end($beginArray); + // 判断当前标签尾的位置是否在栈中最后一个标签头的后面,是则为子标签 + if ($node['end'][1] > $begin['pos']) { + break; + } else { + // 不为子标签时,取出栈中最后一个标签头 + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + // 替换标签尾部 + $content = substr_replace($content, $replace[1], $node['end'][1], strlen($node['end'][0])); + // 把标签头压入栈 + $beginArray[] = ['pos' => $node['begin'][1], 'len' => strlen($node['begin'][0]), 'str' => $replace[0]]; + } + } + + while ($beginArray) { + $begin = array_pop($beginArray); + // 替换标签头部 + $content = substr_replace($content, $begin['str'], $begin['pos'], $begin['len']); + } + } + } + // 自闭合标签 + if (!empty($tags[0])) { + $regex = $this->getRegex(array_keys($tags[0]), 0); + $content = preg_replace_callback($regex, function ($matches) use (&$tags, &$lib) { + // 对应的标签名 + $name = $tags[0][strtolower($matches[1])]; + $alias = $lib . $name != $matches[1] ? ($lib ? strstr($matches[1], $lib) : $matches[1]) : ''; + // 解析标签属性 + $attrs = $this->parseAttr($matches[0], $name, $alias); + $method = 'tag' . $name; + return $this->$method($attrs, ''); + }, $content); + } + + return; + } + + /** + * 按标签生成正则 + * @access public + * @param array|string $tags 标签名 + * @param boolean $close 是否为闭合标签 + * @return string + */ + public function getRegex($tags, $close) + { + $begin = $this->tpl->config('taglib_begin'); + $end = $this->tpl->config('taglib_end'); + $single = strlen(ltrim($begin, '\\')) == 1 && strlen(ltrim($end, '\\')) == 1 ? true : false; + $tagName = is_array($tags) ? implode('|', $tags) : $tags; + + if ($single) { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>[^' . $end . ']*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>[^' . $end . ']*)' . $end; + } + } else { + if ($close) { + // 如果是闭合标签 + $regex = $begin . '(?:(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)|\/(' . $tagName . '))' . $end; + } else { + $regex = $begin . '(' . $tagName . ')\b(?>(?:(?!' . $end . ').)*)' . $end; + } + } + + return '/' . $regex . '/is'; + } + + /** + * 分析标签属性 正则方式 + * @access public + * @param string $str 标签属性字符串 + * @param string $name 标签名 + * @param string $alias 别名 + * @return array + */ + public function parseAttr($str, $name, $alias = '') + { + $regex = '/\s+(?>(?P[\w-]+)\s*)=(?>\s*)([\"\'])(?P(?:(?!\\2).)*)\\2/is'; + $result = []; + + if (preg_match_all($regex, $str, $matches)) { + foreach ($matches['name'] as $key => $val) { + $result[$val] = $matches['value'][$key]; + } + + if (!isset($this->tags[$name])) { + // 检测是否存在别名定义 + foreach ($this->tags as $key => $val) { + if (isset($val['alias'])) { + $array = (array) $val['alias']; + if (in_array($name, explode(',', $array[0]))) { + $tag = $val; + $type = !empty($array[1]) ? $array[1] : 'type'; + $result[$type] = $name; + break; + } + } + } + } else { + $tag = $this->tags[$name]; + // 设置了标签别名 + if (!empty($alias) && isset($tag['alias'])) { + $type = !empty($tag['alias'][1]) ? $tag['alias'][1] : 'type'; + $result[$type] = $alias; + } + } + + if (!empty($tag['must'])) { + $must = explode(',', $tag['must']); + foreach ($must as $name) { + if (!isset($result[$name])) { + throw new Exception('tag attr must:' . $name); + } + } + } + } else { + // 允许直接使用表达式的标签 + if (!empty($this->tags[$name]['expression'])) { + static $_taglibs; + if (!isset($_taglibs[$name])) { + $_taglibs[$name][0] = strlen($this->tpl->config('taglib_begin_origin') . $name); + $_taglibs[$name][1] = strlen($this->tpl->config('taglib_end_origin')); + } + $result['expression'] = substr($str, $_taglibs[$name][0], -$_taglibs[$name][1]); + // 清除自闭合标签尾部/ + $result['expression'] = rtrim($result['expression'], '/'); + $result['expression'] = trim($result['expression']); + } elseif (empty($this->tags[$name]) || !empty($this->tags[$name]['attr'])) { + throw new Exception('tag error:' . $name); + } + } + + return $result; + } + + /** + * 解析条件表达式 + * @access public + * @param string $condition 表达式标签内容 + * @return string + */ + public function parseCondition($condition) + { + if (!strpos($condition, '::') && strpos($condition, ':')) { + $condition = ' ' . substr(strstr($condition, ':'), 1); + } + + $condition = str_ireplace(array_keys($this->comparison), array_values($this->comparison), $condition); + $this->tpl->parseVar($condition); + + // $this->tpl->parseVarFunction($condition); // XXX: 此句能解析表达式中用|分隔的函数,但表达式中如果有|、||这样的逻辑运算就产生了歧异 + return $condition; + } + + /** + * 自动识别构建变量 + * @access public + * @param string $name 变量描述 + * @return string + */ + public function autoBuildVar(&$name) + { + $flag = substr($name, 0, 1); + + if (':' == $flag) { + // 以:开头为函数调用,解析前去掉: + $name = substr($name, 1); + } elseif ('$' != $flag && preg_match('/[a-zA-Z_]/', $flag)) { + // XXX: 这句的写法可能还需要改进 + // 常量不需要解析 + if (defined($name)) { + return $name; + } + + // 不以$开头并且也不是常量,自动补上$前缀 + $name = '$' . $name; + } + + $this->tpl->parseVar($name); + $this->tpl->parseVarFunction($name, false); + + return $name; + } + + /** + * 获取标签列表 + * @access public + * @return array + */ + public function getTags() + { + return $this->tags; + } +} diff --git a/thinkphp/library/think/template/driver/File.php b/thinkphp/library/think/template/driver/File.php new file mode 100644 index 0000000..3b96a0f --- /dev/null +++ b/thinkphp/library/think/template/driver/File.php @@ -0,0 +1,83 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\driver; + +use think\Exception; + +class File +{ + protected $cacheFile; + + /** + * 写入编译缓存 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param string $content 缓存的内容 + * @return void|array + */ + public function write($cacheFile, $content) + { + // 检测模板目录 + $dir = dirname($cacheFile); + + if (!is_dir($dir)) { + mkdir($dir, 0755, true); + } + + // 生成模板缓存文件 + if (false === file_put_contents($cacheFile, $content)) { + throw new Exception('cache write error:' . $cacheFile, 11602); + } + } + + /** + * 读取编译编译 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param array $vars 变量数组 + * @return void + */ + public function read($cacheFile, $vars = []) + { + $this->cacheFile = $cacheFile; + + if (!empty($vars) && is_array($vars)) { + // 模板阵列变量分解成为独立变量 + extract($vars, EXTR_OVERWRITE); + } + + //载入模版缓存文件 + include $this->cacheFile; + } + + /** + * 检查编译缓存是否有效 + * @access public + * @param string $cacheFile 缓存的文件名 + * @param int $cacheTime 缓存时间 + * @return boolean + */ + public function check($cacheFile, $cacheTime) + { + // 缓存文件不存在, 直接返回false + if (!file_exists($cacheFile)) { + return false; + } + + if (0 != $cacheTime && time() > filemtime($cacheFile) + $cacheTime) { + // 缓存是否在有效期 + return false; + } + + return true; + } +} diff --git a/thinkphp/library/think/template/taglib/Cx.php b/thinkphp/library/think/template/taglib/Cx.php new file mode 100644 index 0000000..ad741f2 --- /dev/null +++ b/thinkphp/library/think/template/taglib/Cx.php @@ -0,0 +1,724 @@ + +// +---------------------------------------------------------------------- + +namespace think\template\taglib; + +use think\template\TagLib; + +/** + * CX标签库解析类 + * @category Think + * @package Think + * @subpackage Driver.Taglib + * @author liu21st + */ +class Cx extends Taglib +{ + + // 标签定义 + protected $tags = [ + // 标签定义: attr 属性列表 close 是否闭合(0 或者1 默认1) alias 标签别名 level 嵌套层次 + 'php' => ['attr' => ''], + 'volist' => ['attr' => 'name,id,offset,length,key,mod', 'alias' => 'iterate'], + 'foreach' => ['attr' => 'name,id,item,key,offset,length,mod', 'expression' => true], + 'if' => ['attr' => 'condition', 'expression' => true], + 'elseif' => ['attr' => 'condition', 'close' => 0, 'expression' => true], + 'else' => ['attr' => '', 'close' => 0], + 'switch' => ['attr' => 'name', 'expression' => true], + 'case' => ['attr' => 'value,break', 'expression' => true], + 'default' => ['attr' => '', 'close' => 0], + 'compare' => ['attr' => 'name,value,type', 'alias' => ['eq,equal,notequal,neq,gt,lt,egt,elt,heq,nheq', 'type']], + 'range' => ['attr' => 'name,value,type', 'alias' => ['in,notin,between,notbetween', 'type']], + 'empty' => ['attr' => 'name'], + 'notempty' => ['attr' => 'name'], + 'present' => ['attr' => 'name'], + 'notpresent' => ['attr' => 'name'], + 'defined' => ['attr' => 'name'], + 'notdefined' => ['attr' => 'name'], + 'load' => ['attr' => 'file,href,type,value,basepath', 'close' => 0, 'alias' => ['import,css,js', 'type']], + 'assign' => ['attr' => 'name,value', 'close' => 0], + 'define' => ['attr' => 'name,value', 'close' => 0], + 'for' => ['attr' => 'start,end,name,comparison,step'], + 'url' => ['attr' => 'link,vars,suffix,domain', 'close' => 0, 'expression' => true], + 'function' => ['attr' => 'name,vars,use,call'], + ]; + + /** + * php标签解析 + * 格式: + * {php}echo $name{/php} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPhp($tag, $content) + { + $parseStr = ''; + return $parseStr; + } + + /** + * volist标签解析 循环输出数据集 + * 格式: + * {volist name="userList" id="user" empty=""} + * {user.username} + * {user.email} + * {/volist} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagVolist($tag, $content) + { + $name = $tag['name']; + $id = $tag['id']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $key = !empty($tag['key']) ? $tag['key'] : 'i'; + $mod = isset($tag['mod']) ? $tag['mod'] : '2'; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + // 允许使用函数设定数据集 {$vo.name} + $parseStr = 'autoBuildVar($name); + $parseStr .= '$_result=' . $name . ';'; + $name = '$_result'; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): $' . $key . ' = 0;'; + + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + $parseStr .= '$__LIST__ = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $parseStr .= ' $__LIST__ = ' . $name . ';'; + } + + $parseStr .= 'if( count($__LIST__)==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + $parseStr .= 'foreach($__LIST__ as $key=>$' . $id . '): '; + $parseStr .= '$mod = ($' . $key . ' % ' . $mod . ' );'; + $parseStr .= '++$' . $key . ';?>'; + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + + return; + } + + /** + * foreach标签解析 循环输出数据集 + * 格式: + * {foreach name="userList" id="user" key="key" index="i" mod="2" offset="3" length="5" empty=""} + * {user.username} + * {/foreach} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string|void + */ + public function tagForeach($tag, $content) + { + // 直接使用表达式 + if (!empty($tag['expression'])) { + $expression = ltrim(rtrim($tag['expression'], ')'), '('); + $expression = $this->autoBuildVar($expression); + $parseStr = ''; + $parseStr .= $content; + $parseStr .= ''; + return $parseStr; + } + + $name = $tag['name']; + $key = !empty($tag['key']) ? $tag['key'] : 'key'; + $item = !empty($tag['id']) ? $tag['id'] : $tag['item']; + $empty = isset($tag['empty']) ? $tag['empty'] : ''; + $offset = !empty($tag['offset']) && is_numeric($tag['offset']) ? intval($tag['offset']) : 0; + $length = !empty($tag['length']) && is_numeric($tag['length']) ? intval($tag['length']) : 'null'; + + $parseStr = 'autoBuildVar($name); + $parseStr .= $var . '=' . $name . '; '; + $name = $var; + } else { + $name = $this->autoBuildVar($name); + } + + $parseStr .= 'if(is_array(' . $name . ') || ' . $name . ' instanceof \think\Collection || ' . $name . ' instanceof \think\Paginator): '; + + // 设置了输出数组长度 + if (0 != $offset || 'null' != $length) { + if (!isset($var)) { + $var = '$_' . uniqid(); + } + $parseStr .= $var . ' = is_array(' . $name . ') ? array_slice(' . $name . ',' . $offset . ',' . $length . ', true) : ' . $name . '->slice(' . $offset . ',' . $length . ', true); '; + } else { + $var = &$name; + } + + $parseStr .= 'if( count(' . $var . ')==0 ) : echo "' . $empty . '" ;'; + $parseStr .= 'else: '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + $parseStr .= '$' . $index . '=0; '; + } + + $parseStr .= 'foreach(' . $var . ' as $' . $key . '=>$' . $item . '): '; + + // 设置了索引项 + if (isset($tag['index'])) { + $index = $tag['index']; + if (isset($tag['mod'])) { + $mod = (int) $tag['mod']; + $parseStr .= '$mod = ($' . $index . ' % ' . $mod . '); '; + } + $parseStr .= '++$' . $index . '; '; + } + + $parseStr .= '?>'; + // 循环体中的内容 + $parseStr .= $content; + $parseStr .= ''; + + if (!empty($parseStr)) { + return $parseStr; + } + + return; + } + + /** + * if标签解析 + * 格式: + * {if condition=" $a eq 1"} + * {elseif condition="$a eq 2" /} + * {else /} + * {/if} + * 表达式支持 eq neq gt egt lt elt == > >= < <= or and || && + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagIf($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * elseif标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagElseif($tag, $content) + { + $condition = !empty($tag['expression']) ? $tag['expression'] : $tag['condition']; + $condition = $this->parseCondition($condition); + $parseStr = ''; + + return $parseStr; + } + + /** + * else标签解析 + * 格式:见if标签 + * @access public + * @param array $tag 标签属性 + * @return string + */ + public function tagElse($tag) + { + $parseStr = ''; + + return $parseStr; + } + + /** + * switch标签解析 + * 格式: + * {switch name="a.name"} + * {case value="1" break="false"}1{/case} + * {case value="2" }2{/case} + * {default /}other + * {/switch} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagSwitch($tag, $content) + { + $name = !empty($tag['expression']) ? $tag['expression'] : $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * case标签解析 需要配合switch才有效 + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCase($tag, $content) + { + $value = isset($tag['expression']) ? $tag['expression'] : $tag['value']; + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $value = 'case ' . $value . ':'; + } elseif (strpos($value, '|')) { + $values = explode('|', $value); + $value = ''; + foreach ($values as $val) { + $value .= 'case "' . addslashes($val) . '":'; + } + } else { + $value = 'case "' . $value . '":'; + } + + $parseStr = '' . $content; + $isBreak = isset($tag['break']) ? $tag['break'] : ''; + + if ('' == $isBreak || $isBreak) { + $parseStr .= ''; + } + + return $parseStr; + } + + /** + * default标签解析 需要配合switch才有效 + * 使用: {default /}ddfdf + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefault($tag) + { + $parseStr = ''; + + return $parseStr; + } + + /** + * compare标签解析 + * 用于值的比较 支持 eq neq gt lt egt elt heq nheq 默认是eq + * 格式: {compare name="" type="eq" value="" }content{/compare} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagCompare($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'eq'; // 比较类型 + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } else { + $value = '\'' . $value . '\''; + } + + switch ($type) { + case 'equal': + $type = 'eq'; + break; + case 'notequal': + $type = 'neq'; + break; + } + $type = $this->parseCondition(' ' . $type . ' '); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * range标签解析 + * 如果某个变量存在于某个范围 则输出内容 type= in 表示在范围内 否则表示在范围外 + * 格式: {range name="var|function" value="val" type='in|notin' }content{/range} + * example: {range name="a" value="1,2,3" type='in' }content{/range} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagRange($tag, $content) + { + $name = $tag['name']; + $value = $tag['value']; + $type = isset($tag['type']) ? $tag['type'] : 'in'; // 比较类型 + + $name = $this->autoBuildVar($name); + $flag = substr($value, 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + $str = 'is_array(' . $value . ')?' . $value . ':explode(\',\',' . $value . ')'; + } else { + $value = '"' . $value . '"'; + $str = 'explode(\',\',' . $value . ')'; + } + + if ('between' == $type) { + $parseStr = '= $_RANGE_VAR_[0] && ' . $name . '<= $_RANGE_VAR_[1]):?>' . $content . ''; + } elseif ('notbetween' == $type) { + $parseStr = '$_RANGE_VAR_[1]):?>' . $content . ''; + } else { + $fun = ('in' == $type) ? 'in_array' : '!in_array'; + $parseStr = '' . $content . ''; + } + + return $parseStr; + } + + /** + * present标签解析 + * 如果某个变量已经设置 则输出内容 + * 格式: {present name="" }content{/present} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagPresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * notpresent标签解析 + * 如果某个变量没有设置,则输出内容 + * 格式: {notpresent name="" }content{/notpresent} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotpresent($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * empty标签解析 + * 如果某个变量为empty 则输出内容 + * 格式: {empty name="" }content{/empty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagEmpty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty())): ?>' . $content . ''; + + return $parseStr; + } + + /** + * notempty标签解析 + * 如果某个变量不为empty 则输出内容 + * 格式: {notempty name="" }content{/notempty} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagNotempty($tag, $content) + { + $name = $tag['name']; + $name = $this->autoBuildVar($name); + $parseStr = 'isEmpty()))): ?>' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否已经定义了该常量 + * {defined name='TXT'}已定义{/defined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagDefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * 判断是否没有定义了该常量 + * {notdefined name='TXT'}已定义{/notdefined} + * @access public + * @param array $tag + * @param string $content + * @return string + */ + public function tagNotdefined($tag, $content) + { + $name = $tag['name']; + $parseStr = '' . $content . ''; + + return $parseStr; + } + + /** + * load 标签解析 {load file="/static/js/base.js" /} + * 格式:{load file="/static/css/base.css" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagLoad($tag, $content) + { + $file = isset($tag['file']) ? $tag['file'] : $tag['href']; + $type = isset($tag['type']) ? strtolower($tag['type']) : ''; + + $parseStr = ''; + $endStr = ''; + + // 判断是否存在加载条件 允许使用函数判断(默认为isset) + if (isset($tag['value'])) { + $name = $tag['value']; + $name = $this->autoBuildVar($name); + $name = 'isset(' . $name . ')'; + $parseStr .= ''; + $endStr = ''; + } + + // 文件方式导入 + $array = explode(',', $file); + + foreach ($array as $val) { + $type = strtolower(substr(strrchr($val, '.'), 1)); + switch ($type) { + case 'js': + $parseStr .= ''; + break; + case 'css': + $parseStr .= ''; + break; + case 'php': + $parseStr .= ''; + break; + } + } + + return $parseStr . $endStr; + } + + /** + * assign标签解析 + * 在模板中给某个变量赋值 支持变量赋值 + * 格式: {assign name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagAssign($tag, $content) + { + $name = $this->autoBuildVar($tag['name']); + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * define标签解析 + * 在模板中定义常量 支持变量赋值 + * 格式: {define name="" value="" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagDefine($tag, $content) + { + $name = '\'' . $tag['name'] . '\''; + $flag = substr($tag['value'], 0, 1); + + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($tag['value']); + } else { + $value = '\'' . $tag['value'] . '\''; + } + + $parseStr = ''; + + return $parseStr; + } + + /** + * for标签解析 + * 格式: + * {for start="" end="" comparison="" step="" name=""} + * content + * {/for} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFor($tag, $content) + { + //设置默认值 + $start = 0; + $end = 0; + $step = 1; + $comparison = 'lt'; + $name = 'i'; + $rand = rand(); //添加随机数,防止嵌套变量冲突 + + //获取属性 + foreach ($tag as $key => $value) { + $value = trim($value); + $flag = substr($value, 0, 1); + if ('$' == $flag || ':' == $flag) { + $value = $this->autoBuildVar($value); + } + + switch ($key) { + case 'start': + $start = $value; + break; + case 'end': + $end = $value; + break; + case 'step': + $step = $value; + break; + case 'comparison': + $comparison = $value; + break; + case 'name': + $name = $value; + break; + } + } + + $parseStr = 'parseCondition('$' . $name . ' ' . $comparison . ' $__FOR_END_' . $rand . '__') . ';$' . $name . '+=' . $step . '){ ?>'; + $parseStr .= $content; + $parseStr .= ''; + + return $parseStr; + } + + /** + * url函数的tag标签 + * 格式:{url link="模块/控制器/方法" vars="参数" suffix="true或者false 是否带有后缀" domain="true或者false 是否携带域名" /} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagUrl($tag, $content) + { + $url = isset($tag['link']) ? $tag['link'] : ''; + $vars = isset($tag['vars']) ? $tag['vars'] : ''; + $suffix = isset($tag['suffix']) ? $tag['suffix'] : 'true'; + $domain = isset($tag['domain']) ? $tag['domain'] : 'false'; + + return ''; + } + + /** + * function标签解析 匿名函数,可实现递归 + * 使用: + * {function name="func" vars="$data" call="$list" use="&$a,&$b"} + * {if is_array($data)} + * {foreach $data as $val} + * {~func($val) /} + * {/foreach} + * {else /} + * {$data} + * {/if} + * {/function} + * @access public + * @param array $tag 标签属性 + * @param string $content 标签内容 + * @return string + */ + public function tagFunction($tag, $content) + { + $name = !empty($tag['name']) ? $tag['name'] : 'func'; + $vars = !empty($tag['vars']) ? $tag['vars'] : ''; + $call = !empty($tag['call']) ? $tag['call'] : ''; + $use = ['&$' . $name]; + + if (!empty($tag['use'])) { + foreach (explode(',', $tag['use']) as $val) { + $use[] = '&' . ltrim(trim($val), '&'); + } + } + + $parseStr = '' . $content . '' : '?>'; + + return $parseStr; + } +} diff --git a/thinkphp/library/think/validate/ValidateRule.php b/thinkphp/library/think/validate/ValidateRule.php new file mode 100644 index 0000000..7cd7017 --- /dev/null +++ b/thinkphp/library/think/validate/ValidateRule.php @@ -0,0 +1,171 @@ + +// +---------------------------------------------------------------------- + +namespace think\validate; + +/** + * Class ValidateRule + * @package think\validate + * @method ValidateRule confirm(mixed $rule, string $msg = '') static 验证是否和某个字段的值一致 + * @method ValidateRule different(mixed $rule, string $msg = '') static 验证是否和某个字段的值是否不同 + * @method ValidateRule egt(mixed $rule, string $msg = '') static 验证是否大于等于某个值 + * @method ValidateRule gt(mixed $rule, string $msg = '') static 验证是否大于某个值 + * @method ValidateRule elt(mixed $rule, string $msg = '') static 验证是否小于等于某个值 + * @method ValidateRule lt(mixed $rule, string $msg = '') static 验证是否小于某个值 + * @method ValidateRule eg(mixed $rule, string $msg = '') static 验证是否等于某个值 + * @method ValidateRule in(mixed $rule, string $msg = '') static 验证是否在范围内 + * @method ValidateRule notIn(mixed $rule, string $msg = '') static 验证是否不在某个范围 + * @method ValidateRule between(mixed $rule, string $msg = '') static 验证是否在某个区间 + * @method ValidateRule notBetween(mixed $rule, string $msg = '') static 验证是否不在某个区间 + * @method ValidateRule length(mixed $rule, string $msg = '') static 验证数据长度 + * @method ValidateRule max(mixed $rule, string $msg = '') static 验证数据最大长度 + * @method ValidateRule min(mixed $rule, string $msg = '') static 验证数据最小长度 + * @method ValidateRule after(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule before(mixed $rule, string $msg = '') static 验证日期 + * @method ValidateRule expire(mixed $rule, string $msg = '') static 验证有效期 + * @method ValidateRule allowIp(mixed $rule, string $msg = '') static 验证IP许可 + * @method ValidateRule denyIp(mixed $rule, string $msg = '') static 验证IP禁用 + * @method ValidateRule regex(mixed $rule, string $msg = '') static 使用正则验证数据 + * @method ValidateRule token(mixed $rule='__token__', string $msg = '') static 验证表单令牌 + * @method ValidateRule is(mixed $rule, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isRequire(mixed $rule = null, string $msg = '') static 验证字段必须 + * @method ValidateRule isNumber(mixed $rule = null, string $msg = '') static 验证字段值是否为数字 + * @method ValidateRule isArray(mixed $rule = null, string $msg = '') static 验证字段值是否为数组 + * @method ValidateRule isInteger(mixed $rule = null, string $msg = '') static 验证字段值是否为整形 + * @method ValidateRule isFloat(mixed $rule = null, string $msg = '') static 验证字段值是否为浮点数 + * @method ValidateRule isMobile(mixed $rule = null, string $msg = '') static 验证字段值是否为手机 + * @method ValidateRule isIdCard(mixed $rule = null, string $msg = '') static 验证字段值是否为身份证号码 + * @method ValidateRule isChs(mixed $rule = null, string $msg = '') static 验证字段值是否为中文 + * @method ValidateRule isChsDash(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母及下划线 + * @method ValidateRule isChsAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为中文和字母 + * @method ValidateRule isChsAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为中文字母和数字 + * @method ValidateRule isDate(mixed $rule = null, string $msg = '') static 验证字段值是否为有效格式 + * @method ValidateRule isBool(mixed $rule = null, string $msg = '') static 验证字段值是否为布尔值 + * @method ValidateRule isAlpha(mixed $rule = null, string $msg = '') static 验证字段值是否为字母 + * @method ValidateRule isAlphaDash(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和下划线 + * @method ValidateRule isAlphaNum(mixed $rule = null, string $msg = '') static 验证字段值是否为字母和数字 + * @method ValidateRule isAccepted(mixed $rule = null, string $msg = '') static 验证字段值是否为yes, on, 或是 1 + * @method ValidateRule isEmail(mixed $rule = null, string $msg = '') static 验证字段值是否为有效邮箱格式 + * @method ValidateRule isUrl(mixed $rule = null, string $msg = '') static 验证字段值是否为有效URL地址 + * @method ValidateRule activeUrl(mixed $rule, string $msg = '') static 验证是否为合格的域名或者IP + * @method ValidateRule ip(mixed $rule, string $msg = '') static 验证是否有效IP + * @method ValidateRule fileExt(mixed $rule, string $msg = '') static 验证文件后缀 + * @method ValidateRule fileMime(mixed $rule, string $msg = '') static 验证文件类型 + * @method ValidateRule fileSize(mixed $rule, string $msg = '') static 验证文件大小 + * @method ValidateRule image(mixed $rule, string $msg = '') static 验证图像文件 + * @method ValidateRule method(mixed $rule, string $msg = '') static 验证请求类型 + * @method ValidateRule dateFormat(mixed $rule, string $msg = '') static 验证时间和日期是否符合指定格式 + * @method ValidateRule unique(mixed $rule, string $msg = '') static 验证是否唯一 + * @method ValidateRule behavior(mixed $rule, string $msg = '') static 使用行为类验证 + * @method ValidateRule filter(mixed $rule, string $msg = '') static 使用filter_var方式验证 + * @method ValidateRule requireIf(mixed $rule, string $msg = '') static 验证某个字段等于某个值的时候必须 + * @method ValidateRule requireCallback(mixed $rule, string $msg = '') static 通过回调方法验证某个字段是否必须 + * @method ValidateRule requireWith(mixed $rule, string $msg = '') static 验证某个字段有值的情况下必须 + * @method ValidateRule must(mixed $rule = null, string $msg = '') static 必须验证 + */ +class ValidateRule +{ + // 验证字段的名称 + protected $title; + + // 当前验证规则 + protected $rule = []; + + // 验证提示信息 + protected $message = []; + + /** + * 添加验证因子 + * @access protected + * @param string $name 验证名称 + * @param mixed $rule 验证规则 + * @param string $msg 提示信息 + * @return $this + */ + protected function addItem($name, $rule = null, $msg = '') + { + if ($rule || 0 === $rule) { + $this->rule[$name] = $rule; + } else { + $this->rule[] = $name; + } + + $this->message[] = $msg; + + return $this; + } + + /** + * 获取验证规则 + * @access public + * @return array + */ + public function getRule() + { + return $this->rule; + } + + /** + * 获取验证字段名称 + * @access public + * @return string + */ + public function getTitle() + { + return $this->title; + } + + /** + * 获取验证提示 + * @access public + * @return array + */ + public function getMsg() + { + return $this->message; + } + + /** + * 设置验证字段名称 + * @access public + * @return $this + */ + public function title($title) + { + $this->title = $title; + + return $this; + } + + public function __call($method, $args) + { + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$this, 'addItem'], $args); + } + + public static function __callStatic($method, $args) + { + $rule = new static(); + + if ('is' == strtolower(substr($method, 0, 2))) { + $method = substr($method, 2); + } + + array_unshift($args, lcfirst($method)); + + return call_user_func_array([$rule, 'addItem'], $args); + } +} diff --git a/thinkphp/library/think/view/driver/Php.php b/thinkphp/library/think/view/driver/Php.php new file mode 100644 index 0000000..7948dc0 --- /dev/null +++ b/thinkphp/library/think/view/driver/Php.php @@ -0,0 +1,183 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; + +class Php +{ + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'php', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + ]; + + protected $template; + protected $app; + protected $content; + + public function __construct(App $app, $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @return void + */ + public function fetch($template, $data = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + $this->template = $template; + + // 记录视图信息 + $this->app + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + + extract($data, EXTR_OVERWRITE); + include $this->template; + } + + /** + * 渲染模板内容 + * @access public + * @param string $content 模板内容 + * @param array $data 模板变量 + * @return void + */ + public function display($content, $data = []) + { + $this->content = $content; + + extract($data, EXTR_OVERWRITE); + eval('?>' . $this->content); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + if (empty($this->config['view_path'])) { + $this->config['view_path'] = $this->app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + } + + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); + } else { + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + + /** + * 配置模板引擎 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return void + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return isset($this->config[$name]) ? $this->config[$name] : null; + } else { + $this->config[$name] = $value; + } + } + + public function __debugInfo() + { + return ['config' => $this->config]; + } +} diff --git a/thinkphp/library/think/view/driver/Think.php b/thinkphp/library/think/view/driver/Think.php new file mode 100644 index 0000000..877aee8 --- /dev/null +++ b/thinkphp/library/think/view/driver/Think.php @@ -0,0 +1,192 @@ + +// +---------------------------------------------------------------------- + +namespace think\view\driver; + +use think\App; +use think\exception\TemplateNotFoundException; +use think\Loader; +use think\Template; + +class Think +{ + // 模板引擎实例 + private $template; + private $app; + + // 模板引擎参数 + protected $config = [ + // 默认模板渲染规则 1 解析为小写+下划线 2 全部转换小写 + 'auto_rule' => 1, + // 视图基础目录(集中式) + 'view_base' => '', + // 模板起始路径 + 'view_path' => '', + // 模板文件后缀 + 'view_suffix' => 'html', + // 模板文件名分隔符 + 'view_depr' => DIRECTORY_SEPARATOR, + // 是否开启模板编译缓存,设为false则每次都会重新编译 + 'tpl_cache' => true, + ]; + + public function __construct(App $app, $config = []) + { + $this->app = $app; + $this->config = array_merge($this->config, (array) $config); + + if (empty($this->config['view_path'])) { + $this->config['view_path'] = $app->getModulePath() . 'view' . DIRECTORY_SEPARATOR; + } + + $this->template = new Template($app, $this->config); + } + + /** + * 检测是否存在模板文件 + * @access public + * @param string $template 模板文件或者模板规则 + * @return bool + */ + public function exists($template) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + return is_file($template); + } + + /** + * 渲染模板文件 + * @access public + * @param string $template 模板文件 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function fetch($template, $data = [], $config = []) + { + if ('' == pathinfo($template, PATHINFO_EXTENSION)) { + // 获取模板文件名 + $template = $this->parseTemplate($template); + } + + // 模板不存在 抛出异常 + if (!is_file($template)) { + throw new TemplateNotFoundException('template not exists:' . $template, $template); + } + + // 记录视图信息 + $this->app + ->log('[ VIEW ] ' . $template . ' [ ' . var_export(array_keys($data), true) . ' ]'); + + $this->template->fetch($template, $data, $config); + } + + /** + * 渲染模板内容 + * @access public + * @param string $template 模板内容 + * @param array $data 模板变量 + * @param array $config 模板参数 + * @return void + */ + public function display($template, $data = [], $config = []) + { + $this->template->display($template, $data, $config); + } + + /** + * 自动定位模板文件 + * @access private + * @param string $template 模板文件规则 + * @return string + */ + private function parseTemplate($template) + { + // 分析模板文件规则 + $request = $this->app['request']; + + // 获取视图根目录 + if (strpos($template, '@')) { + // 跨模块调用 + list($module, $template) = explode('@', $template); + } + + if ($this->config['view_base']) { + // 基础视图目录 + $module = isset($module) ? $module : $request->module(); + $path = $this->config['view_base'] . ($module ? $module . DIRECTORY_SEPARATOR : ''); + } else { + $path = isset($module) ? $this->app->getAppPath() . $module . DIRECTORY_SEPARATOR . 'view' . DIRECTORY_SEPARATOR : $this->config['view_path']; + } + + $depr = $this->config['view_depr']; + + if (0 !== strpos($template, '/')) { + $template = str_replace(['/', ':'], $depr, $template); + $controller = Loader::parseName($request->controller()); + + if ($controller) { + if ('' == $template) { + // 如果模板文件名为空 按照默认规则定位 + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $this->getActionTemplate($request); + } elseif (false === strpos($template, $depr)) { + $template = str_replace('.', DIRECTORY_SEPARATOR, $controller) . $depr . $template; + } + } + } else { + $template = str_replace(['/', ':'], $depr, substr($template, 1)); + } + + return $path . ltrim($template, '/') . '.' . ltrim($this->config['view_suffix'], '.'); + } + + protected function getActionTemplate($request) + { + $rule = [$request->action(true), Loader::parseName($request->action(true)), $request->action()]; + $type = $this->config['auto_rule']; + + return isset($rule[$type]) ? $rule[$type] : $rule[0]; + } + + /** + * 配置或者获取模板引擎参数 + * @access private + * @param string|array $name 参数名 + * @param mixed $value 参数值 + * @return mixed + */ + public function config($name, $value = null) + { + if (is_array($name)) { + $this->template->config($name); + $this->config = array_merge($this->config, $name); + } elseif (is_null($value)) { + return $this->template->config($name); + } else { + $this->template->$name = $value; + $this->config[$name] = $value; + } + } + + public function __call($method, $params) + { + return call_user_func_array([$this->template, $method], $params); + } + + public function __debugInfo() + { + return ['config' => $this->config]; + } +} diff --git a/thinkphp/library/traits/controller/Jump.php b/thinkphp/library/traits/controller/Jump.php new file mode 100644 index 0000000..41f7e93 --- /dev/null +++ b/thinkphp/library/traits/controller/Jump.php @@ -0,0 +1,168 @@ +error(); + * $this->redirect(); + * } + * } + */ +namespace traits\controller; + +use think\Container; +use think\exception\HttpResponseException; +use think\Response; +use think\response\Redirect; + +trait Jump +{ + /** + * 应用实例 + * @var \think\App + */ + protected $app; + + /** + * 操作成功跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 + * @return void + */ + protected function success($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + if (is_null($url) && isset($_SERVER["HTTP_REFERER"])) { + $url = $_SERVER["HTTP_REFERER"]; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : Container::get('url')->build($url); + } + + $result = [ + 'code' => 1, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + $type = $this->getResponseType(); + // 把跳转模板的渲染下沉,这样在 response_send 行为里通过getData()获得的数据是一致性的格式 + if ('html' == strtolower($type)) { + $type = 'jump'; + } + + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_success_tmpl')]); + + throw new HttpResponseException($response); + } + + /** + * 操作错误跳转的快捷方法 + * @access protected + * @param mixed $msg 提示信息 + * @param string $url 跳转的URL地址 + * @param mixed $data 返回的数据 + * @param integer $wait 跳转等待时间 + * @param array $header 发送的Header信息 + * @return void + */ + protected function error($msg = '', $url = null, $data = '', $wait = 3, array $header = []) + { + $type = $this->getResponseType(); + if (is_null($url)) { + $url = $this->app['request']->isAjax() ? '' : 'javascript:history.back(-1);'; + } elseif ('' !== $url) { + $url = (strpos($url, '://') || 0 === strpos($url, '/')) ? $url : $this->app['url']->build($url); + } + + $result = [ + 'code' => 0, + 'msg' => $msg, + 'data' => $data, + 'url' => $url, + 'wait' => $wait, + ]; + + if ('html' == strtolower($type)) { + $type = 'jump'; + } + + $response = Response::create($result, $type)->header($header)->options(['jump_template' => $this->app['config']->get('dispatch_error_tmpl')]); + + throw new HttpResponseException($response); + } + + /** + * 返回封装后的API数据到客户端 + * @access protected + * @param mixed $data 要返回的数据 + * @param integer $code 返回的code + * @param mixed $msg 提示信息 + * @param string $type 返回数据格式 + * @param array $header 发送的Header信息 + * @return void + */ + protected function result($data, $code = 0, $msg = '', $type = '', array $header = []) + { + $result = [ + 'code' => $code, + 'msg' => $msg, + 'time' => time(), + 'data' => $data, + ]; + + $type = $type ?: $this->getResponseType(); + $response = Response::create($result, $type)->header($header); + + throw new HttpResponseException($response); + } + + /** + * URL重定向 + * @access protected + * @param string $url 跳转的URL表达式 + * @param array|integer $params 其它URL参数 + * @param integer $code http code + * @param array $with 隐式传参 + * @return void + */ + protected function redirect($url, $params = [], $code = 302, $with = []) + { + $response = new Redirect($url); + + if (is_integer($params)) { + $code = $params; + $params = []; + } + + $response->code($code)->params($params)->with($with); + + throw new HttpResponseException($response); + } + + /** + * 获取当前的response 输出类型 + * @access protected + * @return string + */ + protected function getResponseType() + { + if (!$this->app) { + $this->app = Container::get('app'); + } + + $isAjax = $this->app['request']->isAjax(); + $config = $this->app['config']; + + return $isAjax + ? $config->get('default_ajax_return') + : $config->get('default_return_type'); + } +} diff --git a/thinkphp/logo.png b/thinkphp/logo.png new file mode 100644 index 0000000..25fd059 Binary files /dev/null and b/thinkphp/logo.png differ diff --git a/thinkphp/phpunit.xml.dist b/thinkphp/phpunit.xml.dist new file mode 100644 index 0000000..37c3d2b --- /dev/null +++ b/thinkphp/phpunit.xml.dist @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + ./library/think/*/tests/ + + + + + + ./library/ + + ./library/think/*/tests + ./library/think/*/assets + ./library/think/*/resources + ./library/think/*/vendor + + + + \ No newline at end of file diff --git a/thinkphp/tpl/default_index.tpl b/thinkphp/tpl/default_index.tpl new file mode 100644 index 0000000..e5c1363 --- /dev/null +++ b/thinkphp/tpl/default_index.tpl @@ -0,0 +1,10 @@ +*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333;font-size:18px;} h1{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

    :)

    ThinkPHP V5.1
    12载初心不改(2006-2018) - 你值得信赖的PHP框架

    '; + } +} diff --git a/thinkphp/tpl/dispatch_jump.tpl b/thinkphp/tpl/dispatch_jump.tpl new file mode 100644 index 0000000..583376b --- /dev/null +++ b/thinkphp/tpl/dispatch_jump.tpl @@ -0,0 +1,49 @@ +{__NOLAYOUT__} + + + + + 跳转提示 + + + +
    + + +

    :)

    +

    + + +

    :(

    +

    + + +

    +

    + 页面自动 跳转 等待时间: +

    +
    + + + diff --git a/thinkphp/tpl/page_trace.tpl b/thinkphp/tpl/page_trace.tpl new file mode 100644 index 0000000..2e5afba --- /dev/null +++ b/thinkphp/tpl/page_trace.tpl @@ -0,0 +1,71 @@ +
    + + +
    +
    +
    getUseTime().'s ';?>
    + +
    + + diff --git a/thinkphp/tpl/think_exception.tpl b/thinkphp/tpl/think_exception.tpl new file mode 100644 index 0000000..19ecbdc --- /dev/null +++ b/thinkphp/tpl/think_exception.tpl @@ -0,0 +1,507 @@ +'.end($names).''; + } + } + + if(!function_exists('parse_file')){ + function parse_file($file, $line) + { + return ''.basename($file)." line {$line}".''; + } + } + + if(!function_exists('parse_args')){ + function parse_args($args) + { + $result = []; + + foreach ($args as $key => $item) { + switch (true) { + case is_object($item): + $value = sprintf('object(%s)', parse_class(get_class($item))); + break; + case is_array($item): + if(count($item) > 3){ + $value = sprintf('[%s, ...]', parse_args(array_slice($item, 0, 3))); + } else { + $value = sprintf('[%s]', parse_args($item)); + } + break; + case is_string($item): + if(strlen($item) > 20){ + $value = sprintf( + '\'%s...\'', + htmlentities($item), + htmlentities(substr($item, 0, 20)) + ); + } else { + $value = sprintf("'%s'", htmlentities($item)); + } + break; + case is_int($item): + case is_float($item): + $value = $item; + break; + case is_null($item): + $value = 'null'; + break; + case is_bool($item): + $value = '' . ($item ? 'true' : 'false') . ''; + break; + case is_resource($item): + $value = 'resource'; + break; + default: + $value = htmlentities(str_replace("\n", '', var_export(strval($item), true))); + break; + } + + $result[] = is_int($key) ? $value : "'{$key}' => {$value}"; + } + + return implode(', ', $result); + } + } +?> + + + + + 系统发生错误 + + + + +
    + +
    + +
    +
    + +
    +
    +

    [

    +
    +

    +
    + +
    + +
    +
      $value) { ?>
    +
    + +
    +

    Call Stack

    +
      +
    1. + +
    2. + +
    3. + +
    +
    +
    + +
    + +

    + +
    + + + +
    +

    Exception Datas

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + +
    +

    Environment Variables

    + $value) { ?> + + + + + + + $val) { ?> + + + + + + + +
    empty
    + +
    + +
    + + + + + + + +