├── .gitignore ├── .idea ├── php.xml └── vcs.xml ├── README.md ├── application ├── config │ ├── default.php │ ├── mysql.php │ ├── router.php │ └── template.php ├── controller │ ├── Base.php │ └── Index.php ├── dao │ └── User.php ├── entity │ └── User.php ├── index.php ├── service │ └── User.php └── template │ └── default │ ├── Index │ └── Index.twig │ └── index.twig ├── bin ├── family.service ├── family.sh └── fswatch.sh ├── composer.json ├── framework └── Family │ ├── Core │ ├── Config.php │ ├── Log.php │ ├── Route.php │ └── Singleton.php │ ├── Coroutine │ ├── Context.php │ └── Coroutine.php │ ├── Db │ └── Mysql.php │ ├── Family.php │ ├── Helper │ ├── Dir.php │ └── Template.php │ ├── MVC │ ├── Controller.php │ ├── Dao.php │ ├── Entity.php │ └── View.php │ └── Pool │ ├── Any.php │ ├── Context.php │ ├── Mysql.php │ └── PoolInterface.php └── sql └── test.sql /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | application/log 3 | .idea/* 4 | .idea/php.xml 5 | composer.lock 6 | test/ 7 | vendor/ 8 | application/template/default_cache 9 | *.bak 10 | *.pid -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | family 2 | ========== 3 | 公众号 swoole4.0之打造自己的web开发框架 代码 4 | 5 | 欢迎关注公众号: 6 | ========= 7 | ![avatar](http://mmbiz.qpic.cn/mmbiz/LHDiahSVnXhunbEtuowcI7kF5kmUaeTrszibibQ3st6OU8hy2CoIotHHLxicicibyF1qkNI7HibXYHXGN6hRby4ZyjR6A/640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy=1&wx_co=1) 8 | 9 | 运行 10 | ======== 11 | * 导到sql/test.sql 12 | * 修改application/config/default.php数据库配置 13 | * 运行 php application/index.php 14 | * 浏览器访问:http://127.0.0.1:9501/Index/list -------------------------------------------------------------------------------- /application/config/default.php: -------------------------------------------------------------------------------- 1 | '0.0.0.0', //服务监听ip 4 | 'port' => 9501, //监听端口 5 | 'time_zone' => 'Asia/Shanghai', //时区 6 | 'swoole_setting' => [ //swoole配置 7 | 'worker_num' => 1, //worker进程数量 8 | 'daemonize' => 1, //是否开启守护进程 9 | ] 10 | ]; -------------------------------------------------------------------------------- /application/config/mysql.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'pool_size' => 3, //连接池大小 5 | 'pool_get_timeout' => 0.5, //当在此时间内未获得到一个连接,会立即返回。(表示所以的连接都已在使用中) 6 | 'master' => [ 7 | 'host' => '127.0.0.1', //数据库ip 8 | 'port' => 3306, //数据库端口 9 | 'user' => 'root', //数据库用户名 10 | 'password' => '123456', //数据库密码 11 | 'database' => 'test', //默认数据库名 12 | 'timeout' => 0.5, //数据库连接超时时间 13 | 'charset' => 'utf8mb4', //默认字符集 14 | 'strict_type' => true, //ture,会自动表数字转为int类型 15 | ], 16 | ], 17 | ]; -------------------------------------------------------------------------------- /application/config/router.php: -------------------------------------------------------------------------------- 1 | function (FastRoute\RouteCollector $r) { 4 | $r->addRoute('GET', '/users', ['Index', 'list']); 5 | $r->addRoute('GET', '/user/{uid:\d+}', 'Index@user'); 6 | $r->get('/add', ['Index', 'add']); 7 | $r->get('/test', function () { 8 | return "i am test"; 9 | }); 10 | $r->post('/post', function () { 11 | return "must post method"; 12 | }); 13 | $r->get('/{m:[a-zA-Z0-9]+}', 'Index@{m}'); 14 | } 15 | ]; -------------------------------------------------------------------------------- /application/config/template.php: -------------------------------------------------------------------------------- 1 | [ 7 | //模板页面的存放目录 8 | 'path' => Family::$applicationPath . DS . 'template' . DS . 'default', //模版目录, 空则默认 template/default 9 | //模板缓存页面的存放目录 10 | 'cache' => Family::$applicationPath . DS . 'template' . DS . 'default_cache', //缓存目录, 空则默认 template/default_cache 11 | ] 12 | ]; -------------------------------------------------------------------------------- /application/controller/Base.php: -------------------------------------------------------------------------------- 1 | request->getAttribute(self::_CONTROLLER_KEY_) 24 | . DS . $this->request->getAttribute(self::_METHOD_KEY_) 25 | . '.twig'; 26 | } 27 | return $this->template->render($tplFile, $data); 28 | } 29 | 30 | public function json($data) 31 | { 32 | return json_encode([ 33 | 'code' => 0, 34 | 'msg' => '', 35 | 'data' => $data 36 | ], JSON_UNESCAPED_UNICODE); 37 | } 38 | 39 | public function render($data) 40 | { 41 | return [ 42 | 'code' => 0, 43 | 'msg' => '', 44 | 'data' => $data 45 | ]; 46 | } 47 | 48 | 49 | } -------------------------------------------------------------------------------- /application/controller/Index.php: -------------------------------------------------------------------------------- 1 | template(['name' => 'tong']); 14 | } 15 | 16 | public function tong() 17 | { 18 | return $this->json('i am tong ge'); 19 | } 20 | 21 | /** 22 | * @return false|string 23 | * @throws \Exception 24 | * @desc 返回一个用户信息 25 | */ 26 | public function user() 27 | { 28 | $uid = $this->request->getQueryParam('uid'); 29 | if (empty($uid)) { 30 | throw new \Exception("uid 不能为空 "); 31 | } 32 | $result = UserService::getInstance()->getUserInfoByUId($uid); 33 | return $this->json($result); 34 | } 35 | 36 | /** 37 | * @return false|string 38 | * @desc 返回用户列表 39 | */ 40 | public function list() 41 | { 42 | $result = UserService::getInstance()->getUserInfoList(); 43 | return $this->json($result); 44 | 45 | } 46 | 47 | /** 48 | * @return bool 49 | * @desc 添加用户 50 | */ 51 | public function add() 52 | { 53 | $array = [ 54 | 'name' => $this->request->getQueryParam('name'), 55 | 'password' => $this->request->getQueryParam('password'), 56 | ]; 57 | 58 | return $this->json(UserService::getInstance()->add($array)); 59 | } 60 | 61 | /** 62 | * @return bool 63 | * @throws \Exception 64 | * @desc 更新用户信息 65 | */ 66 | public function update() 67 | { 68 | $array = [ 69 | 'name' => $this->request->getQueryParam('name'), 70 | 'password' => $this->request->getQueryParam('password'), 71 | ]; 72 | $id = $this->request->getQueryParam('id'); 73 | return $this->json(UserService::getInstance()->updateById($array, $id)); 74 | } 75 | 76 | /** 77 | * @return mixed 78 | * @throws \Exception 79 | * @desc 删除用户信息 80 | */ 81 | public function delete() 82 | { 83 | $id = $this->request->getQueryParam('id'); 84 | return $this->json(UserService::getInstance()->deleteById($id)); 85 | } 86 | 87 | } -------------------------------------------------------------------------------- /application/dao/User.php: -------------------------------------------------------------------------------- 1 | fetchById($id); 21 | } 22 | 23 | /** 24 | * @return mixed 25 | * @desc 获取所有用户列表 26 | */ 27 | public function getUserInfoList() 28 | { 29 | return UserDao::getInstance()->fetchAll(); 30 | } 31 | 32 | /** 33 | * @param array $array 34 | * @return bool 35 | * @desc 添加一个用户 36 | */ 37 | public function add(array $array) 38 | { 39 | return UserDao::getInstance()->add($array); 40 | } 41 | 42 | /** 43 | * @param array $array 44 | * @param $id 45 | * @return bool 46 | * @throws \Exception 47 | * @desc 按id更新一个用户 48 | */ 49 | public function updateById(array $array, $id) 50 | { 51 | return UserDao::getInstance()->update($array, "id={$id}"); 52 | } 53 | 54 | /** 55 | * @param $id 56 | * @return mixed 57 | * @throws \Exception 58 | * @desc 按id删除用户 59 | */ 60 | public function deleteById($id) 61 | { 62 | return UserDao::getInstance()->delete("id={$id}"); 63 | } 64 | } -------------------------------------------------------------------------------- /application/template/default/Index/Index.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index 5 | 6 | 7 | 8 |

hehefddd, {{ name }}!!!

9 | 10 | -------------------------------------------------------------------------------- /application/template/default/index.twig: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Index 5 | 6 | 7 | 8 |

{{ name }}

9 | 10 | -------------------------------------------------------------------------------- /bin/family.service: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shenzhe/family/1c396137c203d96d81082ef790c64f66507324c9/bin/family.service -------------------------------------------------------------------------------- /bin/family.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: family application server 4 | # Required-Start: $remote_fs $network 5 | # Required-Stop: $remote_fs $network 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: starts family server 9 | # Description: starts the Family Application daemon 10 | ### END INIT INFO 11 | 12 | 13 | #php路径,如不知道在哪,可以用whereis php尝试 14 | PHP_BIN=`which php` 15 | #bin目录 16 | BIN_PATH=$(cd `dirname $0`; pwd) 17 | #入口文件 18 | SERVER_PATH=$BIN_PATH/.. 19 | #脚本执行地址, 可修改为你的php运行脚本######### 20 | APPLICATION_FILE=$SERVER_PATH/application/index.php 21 | 22 | #获取主进程id 23 | getMasterPid() 24 | { 25 | if [ ! -f "$BIN_PATH/master.pid" ];then 26 | echo '' 27 | else 28 | PID=`cat $BIN_PATH/master.pid` 29 | echo $PID 30 | fi 31 | } 32 | 33 | #获取管理进程id 34 | getManagerPid() 35 | { 36 | if [ ! -f "$BIN_PATH/manager.pid" ];then 37 | echo '' 38 | else 39 | MID=`cat $BIN_PATH/manager.pid` 40 | echo $MID 41 | fi 42 | } 43 | 44 | case "$1" in 45 | #启动服务 46 | start) 47 | PID=`getMasterPid` 48 | if [ -n "$PID" ]; then 49 | echo "server is running" 50 | exit 1 51 | fi 52 | echo "Starting server " 53 | echo `$PHP_BIN $APPLICATION_FILE` 54 | echo " done" 55 | ;; 56 | #停止服务 57 | stop) 58 | PID=`getMasterPid` 59 | if [ -z "$PID" ]; then 60 | echo "server is not running" 61 | exit 1 62 | fi 63 | echo "Gracefully shutting down server " 64 | kill $PID 65 | sleep 1 66 | if [ -n "$PID" ]; then 67 | unlink $BIN_PATH/master.pid 68 | unlink $BIN_PATH/manager.pid 69 | fi 70 | echo " done" 71 | ;; 72 | #查看状态 73 | status) 74 | PID=`getMasterPid` 75 | if [ -n "$PID" ]; then 76 | echo "server is running" 77 | else 78 | echo "server is not running" 79 | fi 80 | ;; 81 | #退出 82 | force-quit) 83 | $0 stop 84 | ;; 85 | #重启 86 | restart) 87 | $0 stop 88 | $0 start 89 | 90 | ;; 91 | #reload 92 | reload) 93 | MID=`getManagerPid` 94 | if [ -z "$MID" ]; then 95 | echo "server is not running" 96 | exit 1 97 | fi 98 | echo "Reload server ing... $MID" 99 | kill -USR1 $MID 100 | echo " done" 101 | ;; 102 | #reload task进程 103 | reloadtask) 104 | 105 | MID=`getManagerPid` 106 | 107 | if [ -z "$MID" ]; then 108 | echo "server is not running" 109 | exit 1 110 | fi 111 | echo "Reload task ing..." 112 | kill -USR2 $MID 113 | echo " done" 114 | ;; 115 | #提示 116 | *) 117 | echo "Usage: $0 {start|stop|force-quit|restart|reload|status}" 118 | exit 1 119 | ;; 120 | esac -------------------------------------------------------------------------------- /bin/fswatch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | DIR=$(cd `dirname $0`; pwd) 3 | checkExt=php 4 | checkTplExt=twig 5 | fswatch $DIR/.. | while read file 6 | do 7 | filename=$(basename "$file") 8 | extension="${filename##*.}" 9 | #php文件改动,则reload 10 | if [ "$extension" == "$checkExt" ];then 11 | #reload代码 12 | $DIR/family.sh reload 13 | fi 14 | 15 | #模板文件改动,则reload 16 | if [ "$extension" == "$checkTplExt" ];then 17 | #reload代码 18 | $DIR/family.sh reload 19 | fi 20 | done 21 | 22 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "family", 3 | "description": "family framework", 4 | "keywords": [ 5 | "swoole", 6 | "mvc", 7 | "coroutine" 8 | ], 9 | "authors": [ 10 | { 11 | "name": "shenzhe", 12 | "email": "shenzhe163@gmail.com" 13 | } 14 | ], 15 | "autoload": { 16 | "psr-4": { 17 | "Family\\": "framework/Family" 18 | } 19 | }, 20 | "require": { 21 | "php": ">=7.0", 22 | "nikic/fast-route": "*", 23 | "easyswoole/http": "^1.0", 24 | "twig/twig": "~1.0", 25 | "ext-swoole": ">=4.2.9", 26 | "ext-seaslog": ">=1.9.0" 27 | } 28 | } -------------------------------------------------------------------------------- /framework/Family/Core/Config.php: -------------------------------------------------------------------------------- 1 | $filelist) { 39 | foreach ($filelist as $file) { 40 | if ('default.php' == $file) { 41 | continue; 42 | } 43 | $filename = $dir . DS . $file; 44 | self::$configMap += include "{$filename}"; 45 | } 46 | } 47 | } 48 | } 49 | 50 | /** 51 | * @param $key 52 | * @param $def 53 | * @desc 读取配置 54 | * @return string|null 55 | * 56 | */ 57 | public static function get($key, $def = null) 58 | { 59 | if (isset(self::$configMap[$key])) { 60 | return self::$configMap[$key]; 61 | } 62 | 63 | return $def; 64 | } 65 | } -------------------------------------------------------------------------------- /framework/Family/Core/Log.php: -------------------------------------------------------------------------------- 1 | $e->getFile(), 174 | '{line}' => $e->getLine(), 175 | '{code}' => $e->getCode(), 176 | '{message}' => $e->getMessage(), 177 | '{trace}' => $e->getTraceAsString(), 178 | ]; 179 | $message = implode(' | ', array_keys($array)); 180 | self::emergency($message, $array); 181 | } 182 | 183 | } -------------------------------------------------------------------------------- /framework/Family/Core/Route.php: -------------------------------------------------------------------------------- 1 | get(); 24 | $request = $context->getRequest(); 25 | $path = $request->getUri()->getPath(); 26 | if ('/favicon.ico' == $path) { 27 | return ''; 28 | } 29 | $r = Config::get('router'); 30 | 31 | 32 | //没有路由配置或者配置不可执行,则走默认路由 33 | if (empty($r) || !is_callable($r)) { 34 | return self::normal($path, $request); 35 | 36 | } 37 | 38 | //引入fastrouter,进行路由检测 39 | $dispatcher = simpleDispatcher($r); 40 | $routeInfo = $dispatcher->dispatch($request->getMethod(), $path); 41 | 42 | //匹配到了 43 | if (Dispatcher::FOUND === $routeInfo[0]) { 44 | //匹配的是数组, 格式:['controllerName', 'MethodName'] 45 | if (is_array($routeInfo[1])) { 46 | if (!empty($routeInfo[2]) && is_array($routeInfo[2])) { 47 | //有默认参数 48 | $params = $request->getQueryParams() + $routeInfo[2]; 49 | $request->withQueryParams($params); 50 | } 51 | $request->withAttribute(Controller::_CONTROLLER_KEY_, $routeInfo[1][0]); 52 | $request->withAttribute(Controller::_METHOD_KEY_, $routeInfo[1][1]); 53 | $controllerName = "controller\\" . $routeInfo[1][0]; 54 | $controller = new $controllerName(); 55 | $methodName = $routeInfo[1][1]; 56 | $result = $controller->$methodName(); 57 | } elseif (is_string($routeInfo[1])) { 58 | //字符串, 格式:controllerName@MethodName 59 | list($controllerName, $methodName) = explode('@', $routeInfo[1]); 60 | if (!empty($routeInfo[2]) && is_array($routeInfo[2])) { 61 | 62 | if('{c}' === $controllerName && !empty($routeInfo[2]['c'])) { 63 | $controllerName = $routeInfo[2]['c']; 64 | unset($routeInfo[2]['c']); 65 | } 66 | 67 | if('{m}' === $methodName && !empty($routeInfo[2]['m'])) { 68 | $methodName = $routeInfo[2]['m']; 69 | unset($routeInfo[2]['m']); 70 | } 71 | 72 | if(!empty($routeInfo[2])) { 73 | //有默认参数 74 | $params = $request->getQueryParams() + $routeInfo[2]; 75 | $request->withQueryParams($params); 76 | } 77 | } 78 | $request->withAttribute(Controller::_CONTROLLER_KEY_, $controllerName); 79 | $request->withAttribute(Controller::_METHOD_KEY_, $methodName); 80 | $controllerName = "controller\\" . $controllerName; 81 | $controller = new $controllerName(); 82 | $result = $controller->$methodName(); 83 | } elseif (is_callable($routeInfo[1])) { 84 | //回调函数,直接执行 85 | $result = $routeInfo[1](...$routeInfo[2]); 86 | } else { 87 | throw new \Exception('router error'); 88 | } 89 | 90 | return $result; 91 | } 92 | 93 | //没找到路由,走默认的路由 http://xxx.com/{controllerName}/{MethodName} 94 | if (Dispatcher::NOT_FOUND === $routeInfo[0]) { 95 | 96 | return self::normal($path, $request); 97 | 98 | } 99 | 100 | //匹配到了,但不允许的http method 101 | if (Dispatcher::METHOD_NOT_ALLOWED === $routeInfo[0]) { 102 | throw new \Exception("METHOD_NOT_ALLOWED"); 103 | } 104 | } 105 | 106 | /** 107 | * @param $path 108 | * @param $request Request 109 | * @return mixed 110 | * @desc 没有匹配到路由,走默认的路由规则 http://xxx.com/{controllerName}/{MethodName} 111 | */ 112 | public static function normal($path, $request) 113 | { 114 | //默认访问 controller/index.php 的 index方法 115 | if (empty($path) || '/' == $path) { 116 | $controllerName = 'Index'; 117 | $methodName = 'Index'; 118 | } else { 119 | $maps = explode('/', $path); 120 | 121 | if (count($maps) < 2) { 122 | $controllerName = 'Index'; 123 | $methodName = 'Index'; 124 | } else { 125 | $controllerName = $maps[1]; 126 | if (empty($maps[2])) { 127 | $methodName = 'Index'; 128 | } else { 129 | $methodName = $maps[2]; 130 | } 131 | } 132 | } 133 | $request->withAttribute(Controller::_CONTROLLER_KEY_, $controllerName); 134 | $controllerName = "controller\\{$controllerName}"; 135 | $request->withAttribute(Controller::_METHOD_KEY_, $methodName); 136 | $controller = new $controllerName(); 137 | return $controller->$methodName(); 138 | } 139 | } -------------------------------------------------------------------------------- /framework/Family/Core/Singleton.php: -------------------------------------------------------------------------------- 1 | request = new Request($request); 29 | $this->response = new Response($response); 30 | } 31 | 32 | /** 33 | * @return Request 34 | */ 35 | public function getRequest() 36 | { 37 | return $this->request; 38 | } 39 | 40 | /** 41 | * @return Response 42 | */ 43 | public function getResponse() 44 | { 45 | return $this->response; 46 | } 47 | 48 | /** 49 | * @param $key 50 | * @param $val 51 | */ 52 | public function set($key, $val) 53 | { 54 | $this->map[$key] = $val; 55 | } 56 | 57 | /** 58 | * @param $key 59 | * @return mixed|null 60 | */ 61 | public function get($key) 62 | { 63 | if (isset($this->map[$key])) { 64 | return $this->map[$key]; 65 | } 66 | 67 | return null; 68 | } 69 | } -------------------------------------------------------------------------------- /framework/Family/Coroutine/Coroutine.php: -------------------------------------------------------------------------------- 1 | "根协程Id"] 14 | */ 15 | public static $idMaps = []; 16 | 17 | /** 18 | * @return mixed 19 | * @desc 获取当前协程id 20 | */ 21 | public static function getId() 22 | { 23 | return SwCo::getuid(); 24 | } 25 | 26 | /** 27 | * @desc 父id自设, onRequest回调后的第一个协程,把根协程Id设置为自己 28 | */ 29 | public static function setBaseId() 30 | { 31 | $id = self::getId(); 32 | self::$idMaps[$id] = $id; 33 | return $id; 34 | } 35 | 36 | /** 37 | * @param null $id 38 | * @param int $cur 39 | * @return int|mixed|null 40 | * @desc 获取当前协程根协程id 41 | */ 42 | public static function getPid($id = null, $cur = 1) 43 | { 44 | if ($id === null) { 45 | $id = self::getId(); 46 | } 47 | if (isset(self::$idMaps[$id])) { 48 | return self::$idMaps[$id]; 49 | } 50 | return $cur ? $id : -1; 51 | } 52 | 53 | /** 54 | * @return bool 55 | * @throws \Exception 56 | * @desc 判断是否是根协程 57 | */ 58 | public static function checkBaseCo() 59 | { 60 | $id = SwCo::getuid(); 61 | if (empty(self::$idMaps[$id])) { 62 | return false; 63 | } 64 | 65 | if ($id !== self::$idMaps[$id]) { 66 | return false; 67 | } 68 | 69 | return true; 70 | } 71 | 72 | /** 73 | * @param $cb //协程执行方法 74 | * @param null $deferCb //defer方法 75 | * @return mixed 76 | * @从协程中创建协程,可保持根协程id的传递 77 | */ 78 | public static function create($cb, $deferCb = null) 79 | { 80 | $nid = self::getId(); 81 | return go(function () use ($cb, $deferCb, $nid) { 82 | $id = SwCo::getuid(); 83 | defer(function () use ($deferCb, $id) { 84 | self::call($deferCb); 85 | self::clear($id); 86 | }); 87 | 88 | $pid = self::getPid($nid); 89 | if ($pid == -1) { 90 | $pid = $nid; 91 | } 92 | self::$idMaps[$id] = $pid; 93 | self::call($cb); 94 | }); 95 | } 96 | 97 | /** 98 | * @param $cb 99 | * @param $args 100 | * @return null 101 | * @desc 执行回调函数 102 | */ 103 | public static function call($cb, $args) 104 | { 105 | if (empty($cb)) { 106 | return null; 107 | } 108 | $ret = null; 109 | if (\is_object($cb) || (\is_string($cb) && \function_exists($cb))) { 110 | $ret = $cb(...$args); 111 | } elseif (\is_array($cb)) { 112 | list($obj, $mhd) = $cb; 113 | $ret = \is_object($obj) ? $obj->$mhd(...$args) : $obj::$mhd(...$args); 114 | } 115 | return $ret; 116 | } 117 | 118 | /** 119 | * @param null $id 120 | * @desc 协程退出,清除关系树 121 | */ 122 | public static function clear($id = null) 123 | { 124 | if (null === $id) { 125 | $id = self::getId(); 126 | } 127 | unset(self::$idMaps[$id]); 128 | } 129 | } -------------------------------------------------------------------------------- /framework/Family/Db/Mysql.php: -------------------------------------------------------------------------------- 1 | connect($config['master']); 28 | if ($res === false) { 29 | //连接失败,抛弃常 30 | throw new \Exception($master->connect_error, $master->errno); 31 | } else { 32 | //存入master资源 33 | $this->master = $master; 34 | } 35 | 36 | if (!empty($config['slave'])) { 37 | //创建从数据库连接 38 | foreach ($config['slave'] as $conf) { 39 | $slave = new SwMySql(); 40 | $res = $slave->connect($conf); 41 | if ($res === false) { 42 | //连接失败,抛弃常 43 | throw new \Exception($slave->connect_error, $slave->errno); 44 | } else { 45 | //存入slave资源 46 | $this->slave[] = $slave; 47 | } 48 | } 49 | } 50 | 51 | $this->config = $config; 52 | return $res; 53 | } 54 | 55 | /** 56 | * @param $type 57 | * @param $index 58 | * @return MySQL 59 | * @desc 单个数据库重连 60 | * @throws \Exception 61 | */ 62 | public function reconnect($type, $index) 63 | { 64 | //通过type判断是主还是从 65 | if ('master' == $type) { 66 | //创建主数据连接 67 | $master = new SwMySql(); 68 | $res = $master->connect($this->config['master']); 69 | if ($res === false) { 70 | //连接失败,抛弃常 71 | throw new \Exception($master->connect_error, $master->errno); 72 | } else { 73 | //更新主库连接 74 | $this->master = $master; 75 | } 76 | return $this->master; 77 | } 78 | 79 | if (!empty($this->config['slave'])) { 80 | //创建从数据连接 81 | $slave = new SwMySql(); 82 | $res = $slave->connect($this->config['slave'][$index]); 83 | if ($res === false) { 84 | //连接失败,抛弃常 85 | throw new \Exception($slave->connect_error, $slave->errno); 86 | } else { 87 | //更新对应的重库连接 88 | $this->slave[$index] = $slave; 89 | } 90 | return $slave; 91 | } 92 | } 93 | 94 | /** 95 | * @param $name 96 | * @param $arguments 97 | * @return mixed 98 | * @desc 利用__call,实现操作mysql,并能做断线重连等相关检测 99 | * @throws \Exception 100 | */ 101 | public function __call($name, $arguments) 102 | { 103 | $sql = $arguments[0]; 104 | $res = $this->chooseDb($sql); 105 | $db = $res['db']; 106 | // $result = call_user_func_array([$db, $name], $arguments); 107 | $result = $db->$name($sql); 108 | Log::info($sql); 109 | if (false === $result) { 110 | Log::warning('mysql query:{sql} false', ['{sql}' => $sql]); 111 | if (!$db->connected) { //断线重连 112 | $db = $this->reconnect($res['type'], $res['index']); 113 | Log::info('mysql reconnect', $res); 114 | $result = $db->$name($sql); 115 | return $this->parseResult($result, $db); 116 | } 117 | 118 | if (!empty($db->errno)) { //有错误码,则抛出弃常 119 | throw new \Exception($db->error, $db->errno); 120 | } 121 | } 122 | return $this->parseResult($result, $db); 123 | } 124 | 125 | /** 126 | * @param $result 127 | * @param $db MySQL 128 | * @return array 129 | * @desc 格式化返回结果:查询:返回结果集,插入:返回新增id, 更新删除等操作:返回影响行数 130 | */ 131 | public function parseResult($result, $db) 132 | { 133 | if ($result === true) { 134 | return [ 135 | 'affected_rows' => $db->affected_rows, 136 | 'insert_id' => $db->insert_id, 137 | ]; 138 | } 139 | return $result; 140 | } 141 | 142 | 143 | /** 144 | * @param $sql 145 | * @desc 根据sql语句,选择主还是从 146 | * @ 判断有select 则选择从库, insert, update, delete等选择主库 147 | * @return array 148 | */ 149 | protected function chooseDb($sql) 150 | { 151 | if (!empty($this->slave)) { 152 | //查询语句,随机选择一个从库 153 | if ('select' == strtolower(substr($sql, 0, 6))) { 154 | if (1 == count($this->slave)) { 155 | $index = 0; 156 | } else { 157 | $index = array_rand($this->slave); 158 | } 159 | return [ 160 | 'type' => 'slave', 161 | 'index' => $index, 162 | 'db' => $this->slave[$index], 163 | 164 | ]; 165 | } 166 | } 167 | 168 | return [ 169 | 'type' => 'master', 170 | 'index' => 0, 171 | 'db' => $this->master 172 | ]; 173 | } 174 | 175 | /** 176 | * @desc 回收资源 177 | */ 178 | public function release() 179 | { 180 | $this->master->close(); 181 | if (!empty($this->slave)) { 182 | foreach ($this->slave as $slave) { 183 | $slave->close(); 184 | } 185 | } 186 | } 187 | 188 | /** 189 | * @return mixed 190 | * @desc 返回配置信息 191 | */ 192 | public function getConfig() 193 | { 194 | return $this->config; 195 | } 196 | 197 | } 198 | -------------------------------------------------------------------------------- /framework/Family/Family.php: -------------------------------------------------------------------------------- 1 | set(Config::get('swoole_setting')); 51 | $http->on('start', function (\swoole_server $serv) { 52 | //服务启动 53 | //日志初始化 54 | Log::init(); 55 | file_put_contents(self::$rootPath . DS . 'bin' . DS . 'master.pid', $serv->master_pid); 56 | file_put_contents(self::$rootPath . DS . 'bin' . DS . 'manager.pid', $serv->manager_pid); 57 | Log::info("http server start! {host}: {port}, masterId:{masterId}, managerId: {managerId}", [ 58 | '{host}' => Config::get('host'), 59 | '{port}' => Config::get('port'), 60 | '{masterId}' => $serv->master_pid, 61 | '{managerId}' => $serv->manager_pid, 62 | ]); 63 | }); 64 | 65 | $http->on('shutdown', function () { 66 | //服务关闭,删除进程id 67 | unlink(self::$rootPath . 'DS' . 'bin' . DS . 'master.pid'); 68 | unlink(self::$rootPath . 'DS' . 'bin' . DS . 'manager.pid'); 69 | Log::info("http server shutdown"); 70 | }); 71 | $http->on('workerStart', function (\swoole_http_server $serv, int $worker_id) { 72 | if (1 == $worker_id) { 73 | if (function_exists('opcache_reset')) { 74 | //清除opcache 缓存,swoole模式下其实可以关闭opcache 75 | \opcache_reset(); 76 | } 77 | } 78 | try { 79 | //加载配置,让此处加载的配置可热更新 80 | Config::loadLazy(); 81 | //日志初始化 82 | Log::init(); 83 | $mysqlConfig = Config::get('mysql'); 84 | if (!empty($mysqlConfig)) { 85 | //配置了mysql, 初始化mysql连接池 86 | Pool\Mysql::getInstance($mysqlConfig); 87 | } 88 | 89 | } catch (\Exception $e) { 90 | //初始化异常,关闭服务 91 | print_r($e); 92 | $serv->shutdown(); 93 | } catch (\Throwable $throwable) { 94 | //初始化异常,关闭服务 95 | print_r($throwable); 96 | $serv->shutdown(); 97 | } 98 | }); 99 | $http->on('request', function (\swoole_http_request $request, \swoole_http_response $response) { 100 | //初始化根协程ID 101 | Coroutine::setBaseId(); 102 | //初始化上下文 103 | $context = new Context($request, $response); 104 | //存放容器pool 105 | Pool\Context::getInstance()->put($context); 106 | //协程退出,自动清空 107 | defer(function () { 108 | //清空当前pool的上下文,释放资源 109 | Pool\Context::getInstance()->release(); 110 | }); 111 | try { 112 | //自动路由 113 | $result = Route::dispatch(); 114 | $response->end($result); 115 | } catch (\Exception $e) { //程序异常 116 | Log::exception($e); 117 | $context->getResponse()->withStatus(500); 118 | } catch (\Error $e) { //程序错误,如fatal error 119 | Log::exception($e); 120 | $context->getResponse()->withStatus(500); 121 | } catch (\Throwable $e) { //兜底 122 | Log::exception($e); 123 | $context->getResponse()->withStatus(500); 124 | } 125 | }); 126 | $http->start(); 127 | } catch (\Exception $e) { 128 | print_r($e); 129 | } catch (\Throwable $throwable) { 130 | print_r($throwable); 131 | } 132 | } 133 | 134 | 135 | /** 136 | * @param $class 137 | * @desc 自动加载类 138 | */ 139 | final public static function autoLoader($class) 140 | { 141 | 142 | //把类转为目录,eg \a\b\c => /a/b/c.php 143 | $classPath = \str_replace('\\', DIRECTORY_SEPARATOR, $class) . '.php'; 144 | 145 | //约定框架类都在framework目录下, 业务类都在application下 146 | $findPath = [ 147 | self::$rootPath . DIRECTORY_SEPARATOR . 'application' . DIRECTORY_SEPARATOR, 148 | ]; 149 | 150 | 151 | //遍历目录,查找文件 152 | foreach ($findPath as $path) { 153 | //如果找到文件,则require进来 154 | $realPath = $path . $classPath; 155 | if (is_file($realPath)) { 156 | require "{$realPath}"; 157 | return; 158 | } 159 | } 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /framework/Family/Helper/Dir.php: -------------------------------------------------------------------------------- 1 | isDot()) { 37 | continue; 38 | } 39 | $filename = $file->getFilename(); 40 | if ($file->isDir()) { 41 | self::tree($dir . DS . $filename, $filter, $deep, $result); 42 | 43 | } else { 44 | if (!empty($filter) && !\preg_match($filter, $filename)) { 45 | continue; 46 | } 47 | $result[$dir][] = $filename; 48 | } 49 | } 50 | return $result; 51 | } catch (\Exception $e) { 52 | return false; 53 | } 54 | } 55 | 56 | /** 57 | * 递归删除目录 58 | * @param $dir 59 | * @param $filter 60 | * @return bool 61 | */ 62 | public static function del($dir, $filter = '') 63 | { 64 | $files = new \DirectoryIterator($dir); 65 | foreach ($files as $file) { 66 | if ($file->isDot()) { 67 | continue; 68 | } 69 | $filename = $file->getFilename(); 70 | if (!empty($filter) && !\preg_match($filter, $filename)) { 71 | continue; 72 | } 73 | if ($file->isDir()) { 74 | self::del($dir . DS . $filename); 75 | } else { 76 | \unlink($dir . DS . $filename); 77 | } 78 | } 79 | return \rmdir($dir); 80 | } 81 | } -------------------------------------------------------------------------------- /framework/Family/Helper/Template.php: -------------------------------------------------------------------------------- 1 | template = new Environment($loader, array( 22 | 'cache' => $templateConfig['cache'], 23 | 'auto_reload' => true 24 | )); 25 | } 26 | 27 | public function clear() 28 | { 29 | if ($this->template) { 30 | $this->template->clearTemplateCache(); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /framework/Family/MVC/Controller.php: -------------------------------------------------------------------------------- 1 | get(); 32 | $this->request = $context->getRequest(); 33 | $this->template = Template::getInstance()->template; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /framework/Family/MVC/Dao.php: -------------------------------------------------------------------------------- 1 | entity = $entity; 46 | $entityRef = new \ReflectionClass($this->entity); 47 | $this->table = $entityRef->getConstant('TABLE_NAME'); 48 | $this->pkId = $entityRef->getConstant('PK_ID'); 49 | $this->dbTag = $dbTag; 50 | } 51 | 52 | /** 53 | * @param $dbTag 54 | * @desc 更换数据库连接池 55 | */ 56 | public function setDbName($dbTag) 57 | { 58 | $this->dbTag = $dbTag; 59 | } 60 | 61 | /** 62 | * @return Mysql 63 | * @throws \Exception 64 | */ 65 | public function getDb() 66 | { 67 | $coId = Coroutine::getId(); 68 | if (empty($this->dbs[$coId])) { 69 | //不同协程不能复用mysql连接,所以通过协程id进行资源隔离 70 | //达到同一协程只用一个mysql连接,不同协程用不同的mysql连接 71 | if ($this->dbTag) { 72 | $mysqlConfig = Config::get($this->dbTag); 73 | } else { 74 | $mysqlConfig = null; 75 | } 76 | $this->dbs[$coId] = MysqlPool::getInstance($mysqlConfig)->get(); 77 | defer(function () { 78 | //利用协程的defer特性,自动回收资源 79 | $this->recycle(); 80 | }); 81 | } 82 | return $this->dbs[$coId]; 83 | } 84 | 85 | /** 86 | * @throws \Exception 87 | * @desc mysql资源回收到连接池 88 | */ 89 | public function recycle() 90 | { 91 | $coId = Coroutine::getId(); 92 | if (!empty($this->dbs[$coId])) { 93 | $mysql = $this->dbs[$coId]; 94 | MysqlPool::getInstance($mysql->getConfig())->put($mysql); 95 | unset($this->dbs[$coId]); 96 | } 97 | } 98 | 99 | /** 100 | * @return mixed 101 | * @desc 获取表名 102 | */ 103 | public function getLibName() 104 | { 105 | return $this->table; 106 | } 107 | 108 | /** 109 | * @param $id 110 | * @param string $fields 111 | * @return mixed 112 | * @desc 通过主键查询记录 113 | */ 114 | public function fetchById($id, $fields = '*') 115 | { 116 | return $this->fetchEntity("{$this->pkId} = {$id}", $fields); 117 | } 118 | 119 | /** 120 | * @param string $where 121 | * @param string $fields 122 | * @param null $orderBy 123 | * @return mixed 124 | * @desc 通过条件查询一条记录,并返回一个entity 125 | */ 126 | public function fetchEntity($where = '1', $fields = '*', $orderBy = null) 127 | { 128 | $result = $this->fetchArray($where, $fields, $orderBy, 1); 129 | if (!empty($result[0])) { 130 | return new $this->entity($result[0]); 131 | } 132 | return null; 133 | } 134 | 135 | /** 136 | * @param string $where 137 | * @param string $fields 138 | * @param null $orderBy 139 | * @param int $limit 140 | * @return mixed 141 | * @desc 通过条件查询记录列表,并返回entity列表 142 | */ 143 | public function fetchAll($where = '1', $fields = '*', $orderBy = null, $limit = 0) 144 | { 145 | $result = $this->fetchArray($where, $fields, $orderBy, $limit); 146 | if (empty($result)) { 147 | return $result; 148 | } 149 | foreach ($result as $index => $value) { 150 | $result[$index] = new $this->entity($value); 151 | } 152 | return $result; 153 | } 154 | 155 | 156 | /** 157 | * @param string $where 158 | * @param string $fields 159 | * @param null $orderBy 160 | * @param int $limit 161 | * @return mixed 162 | * @desc 通过条件查询 163 | * @throws \Exception 164 | */ 165 | public function fetchArray($where = '1', $fields = '*', $orderBy = null, $limit = 0) 166 | { 167 | $query = "SELECT {$fields} FROM {$this->getLibName()} WHERE {$where}"; 168 | 169 | if ($orderBy) { 170 | $query .= " order by {$orderBy}"; 171 | } 172 | 173 | if ($limit) { 174 | $query .= " limit {$limit}"; 175 | } 176 | return $this->getDb()->query($query); 177 | } 178 | 179 | /** 180 | * @param array $array 181 | * @return bool 182 | * @desc 插入一条记录 183 | * @throws \Exception 184 | */ 185 | public function add(array $array) 186 | { 187 | 188 | $strFields = '`' . implode('`,`', array_keys($array)) . '`'; 189 | $strValues = "'" . implode("','", array_values($array)) . "'"; 190 | $query = "INSERT INTO {$this->getLibName()} ({$strFields}) VALUES ({$strValues})"; 191 | if (!empty($onDuplicate)) { 192 | $query .= 'ON DUPLICATE KEY UPDATE ' . $onDuplicate; 193 | } 194 | $result = $this->getDb()->query($query); 195 | if (!empty($result['insert_id'])) { 196 | return $result['insert_id']; 197 | } 198 | 199 | return false; 200 | } 201 | 202 | /** 203 | * @param array $array 204 | * @param $where 205 | * @return bool 206 | * @throws \Exception 207 | * @desc 按条件更新记录 208 | */ 209 | public function update(array $array, $where) 210 | { 211 | if (empty($where)) { 212 | throw new \Exception('update 必需有where条件限定'); 213 | } 214 | $strUpdateFields = ''; 215 | foreach ($array as $key => $value) { 216 | $strUpdateFields .= "`{$key}` = '{$value}',"; 217 | } 218 | $strUpdateFields = rtrim($strUpdateFields, ','); 219 | $query = "UPDATE {$this->getLibName()} SET {$strUpdateFields} WHERE {$where}"; 220 | $result = $this->getDb()->query($query); 221 | return $result['affected_rows']; 222 | } 223 | 224 | /** 225 | * @param $where 226 | * @return mixed 227 | * @throws \Exception 228 | * @desc 按条件删除记录 229 | */ 230 | public function delete($where) 231 | { 232 | if (empty($where)) { 233 | throw new \Exception('delete 必需有where条件限定'); 234 | } 235 | 236 | $query = "DELETE FROM {$this->getLibName()} WHERE {$where}"; 237 | $result = $this->getDb()->query($query); 238 | return $result['affected_rows']; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /framework/Family/MVC/Entity.php: -------------------------------------------------------------------------------- 1 | $value) { 20 | if (property_exists($this, $key)) { 21 | $this->$key = $value; 22 | } 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /framework/Family/MVC/View.php: -------------------------------------------------------------------------------- 1 | tag = $tag; 25 | $this->length = $length; 26 | $this->timeout = $timeout; 27 | $this->pool = new chan($this->length); 28 | } 29 | 30 | /** 31 | * @param $resource 32 | * @throws \Exception 33 | * @desc 放入一个mysql连接入池 34 | */ 35 | public function put($resource) 36 | { 37 | if ($this->getLength() >= $this->length) { 38 | throw new \Exception("pool full"); 39 | } 40 | $this->pool->push($resource); 41 | } 42 | 43 | /** 44 | * @return mixed 45 | * @desc 获取一个连接,当超时,返回一个异常 46 | * @throws \Exception 47 | */ 48 | public function get() 49 | { 50 | $mysql = $this->pool->pop($this->timeout); 51 | if (false === $mysql) { 52 | throw new \Exception("get mysql timeout, all mysql connection is used"); 53 | } 54 | return $mysql; 55 | } 56 | 57 | /** 58 | * @return mixed 59 | * @desc 获取当时连接池可用对象 60 | */ 61 | public function getLength() 62 | { 63 | return $this->pool->length(); 64 | } 65 | 66 | public function release() 67 | { 68 | if ($this->getLength() < $this->config['pool_size']) { 69 | //还有未归源的资源 70 | return true; 71 | } 72 | for ($i = 0; $i < $this->length; $i++) { 73 | $resource = $this->pool->pop($this->timeout); 74 | if (false !== $resource) { 75 | if (is_object($resource) 76 | && method_exists($resource, 'release')) { 77 | $resource->release(); 78 | } 79 | unset($resource); 80 | } 81 | } 82 | return $this->pool->close(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /framework/Family/Pool/Context.php: -------------------------------------------------------------------------------- 1 | pool[$id])) { 31 | return $this->pool[$id]; 32 | } 33 | 34 | return null; 35 | } 36 | 37 | /** 38 | * @desc 清除context 39 | */ 40 | public function release() 41 | { 42 | $id = Coroutine::getPid(); 43 | if (isset($this->pool[$id])) { 44 | unset($this->pool[$id]); 45 | Coroutine::clear($id); 46 | } 47 | } 48 | 49 | /** 50 | * @param $context 51 | * @desc 设置context 52 | */ 53 | public function put($context) 54 | { 55 | $id = Coroutine::getPid(); 56 | $this->pool[$id] = $context; 57 | } 58 | 59 | public function getLength() 60 | { 61 | return count($this->pool); 62 | } 63 | } -------------------------------------------------------------------------------- /framework/Family/Pool/Mysql.php: -------------------------------------------------------------------------------- 1 | pool)) { 51 | $this->config = $config; 52 | $this->pool = new chan($config['pool_size']); 53 | for ($i = 0; $i < $config['pool_size']; $i++) { 54 | $mysql = new DB(); 55 | $res = $mysql->connect($config); 56 | if ($res == false) { 57 | //连接失败,抛弃常 58 | throw new \Exception("failed to connect mysql server."); 59 | } else { 60 | //mysql连接存入channel 61 | $this->put($mysql); 62 | } 63 | } 64 | } 65 | } 66 | 67 | /** 68 | * @param $mysql 69 | * @throws \Exception 70 | * @desc 放入一个mysql连接入池 71 | */ 72 | public function put($mysql) 73 | { 74 | if ($this->getLength() >= $this->config['pool_size']) { 75 | throw new \Exception("pool full"); 76 | } 77 | $this->pool->push($mysql); 78 | } 79 | 80 | /** 81 | * @return mixed 82 | * @desc 获取一个连接,当超时,返回一个异常 83 | * @throws \Exception 84 | */ 85 | public function get() 86 | { 87 | $mysql = $this->pool->pop($this->config['pool_get_timeout']); 88 | if (false === $mysql) { 89 | throw new \Exception("get mysql timeout, all mysql connection is used"); 90 | } 91 | return $mysql; 92 | } 93 | 94 | /** 95 | * @return mixed 96 | * @desc 获取当时连接池可用对象 97 | */ 98 | public function getLength() 99 | { 100 | return $this->pool->length(); 101 | } 102 | 103 | /** 104 | * @return bool|mixed 105 | * @desc 回收处理 106 | */ 107 | public function release() 108 | { 109 | if ($this->getLength() < $this->config['pool_size']) { 110 | //还有未归源的资源 111 | return true; 112 | } 113 | for ($i = 0; $i < $this->config['pool_size']; $i++) { 114 | $db = $this->pool->pop($this->config['pool_get_timeout']); 115 | if (false !== $db) { 116 | $db->release(); 117 | } 118 | } 119 | return $this->pool->close(); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /framework/Family/Pool/PoolInterface.php: -------------------------------------------------------------------------------- 1 |