├── .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 | 
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 |