├── php ├── cron │ └── root ├── supervisor.d │ ├── php.ini │ └── cron.ini ├── php.ini-development ├── Dockerfile ├── php.ini-production └── supervisord.conf ├── .gitignore ├── src ├── app │ ├── Http │ │ ├── Model │ │ │ ├── UserSignInModel.php │ │ │ ├── Model.php │ │ │ └── UserModel.php │ │ ├── Constant │ │ │ ├── UserConstant.php │ │ │ ├── EntityConstant.php │ │ │ └── QueueConstant.php │ │ ├── Service │ │ │ ├── Service.php │ │ │ └── UserService.php │ │ ├── Exception │ │ │ ├── UserException.php │ │ │ ├── DBException.php │ │ │ └── CommonException.php │ │ ├── Controller │ │ │ ├── Controller.php │ │ │ └── UserController.php │ │ ├── Response │ │ │ ├── BusinessResponse.php │ │ │ ├── UserListResponse.php │ │ │ └── UserResponse.php │ │ ├── Queue │ │ │ ├── UserQueue.php │ │ │ └── Common.php │ │ ├── Request │ │ │ ├── UserIdRequest.php │ │ │ ├── UserRequest.php │ │ │ └── CommonRequest.php │ │ ├── Entity │ │ │ ├── UserSignIn.php │ │ │ └── User.php │ │ ├── Command │ │ │ └── GenerateRandomUserCommand.php │ │ └── Business │ │ │ └── UserBusiness.php │ └── Bootstrap │ │ ├── Component │ │ ├── ComponentInterface.php │ │ ├── Guzzle.php │ │ ├── Cache.php │ │ ├── EntityManager.php │ │ ├── Logger.php │ │ ├── DoctrineLogger.php │ │ └── Queue.php │ │ ├── middleware.php │ │ ├── route.php │ │ ├── component.php │ │ ├── error.php │ │ ├── app.php │ │ ├── Error │ │ ├── Response.php │ │ ├── Exception.php │ │ └── Error.php │ │ └── Middleware │ │ └── CORSMiddleware.php ├── config │ ├── log.php │ ├── cache.php │ ├── mail.php │ ├── queue.php │ ├── database.php │ ├── route.yaml │ └── app.php ├── public │ ├── .htaccess │ ├── index.php │ ├── command.php │ └── queue.php ├── .env.example ├── LICENSE.md └── composer.json ├── rabbitmq ├── Dockerfile └── rabbitmq.conf ├── .env.example ├── nginx └── default.conf ├── mysql └── MySQL.sql ├── docker-compose.production.yml ├── docker-compose.development.yml └── README.md /php/cron/root: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /src/vendor 2 | /src/.idea 3 | /src/var 4 | /src/.env 5 | /nginx/log 6 | /mysql/log 7 | /.idea 8 | .env -------------------------------------------------------------------------------- /php/supervisor.d/php.ini: -------------------------------------------------------------------------------- 1 | [program:php] 2 | command=/usr/local/sbin/php-fpm --nodaemonize 3 | autostart=true 4 | autorestart=true -------------------------------------------------------------------------------- /src/app/Http/Model/UserSignInModel.php: -------------------------------------------------------------------------------- 1 | getenv('LOG_NAME') ? : 'Logger', 5 | 'path' => getenv('LOG_PATH') ? : '/var/log' 6 | ]; -------------------------------------------------------------------------------- /src/public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine On 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteRule ^ index.php [QSA,L] -------------------------------------------------------------------------------- /src/config/cache.php: -------------------------------------------------------------------------------- 1 | getenv("REDIS_HOST") ? : '127.0.0.1', 5 | 'port' => getenv("REDIS_PORT") ? : '6379', 6 | ]; -------------------------------------------------------------------------------- /rabbitmq/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base Image 2 | FROM rabbitmq:3.8.14-management-alpine 3 | # Enable Plugin 4 | RUN rabbitmq-plugins enable --offline rabbitmq_mqtt rabbitmq_web_mqtt -------------------------------------------------------------------------------- /php/supervisor.d/cron.ini: -------------------------------------------------------------------------------- 1 | [program:cron] 2 | command=/usr/sbin/crond -f 3 | process_name=%(program_name)s_%(process_num)02d 4 | numprocs=1 5 | autostart=true 6 | autorestart=true -------------------------------------------------------------------------------- /src/public/index.php: -------------------------------------------------------------------------------- 1 | run(); -------------------------------------------------------------------------------- /src/app/Http/Constant/UserConstant.php: -------------------------------------------------------------------------------- 1 | getenv('MAIL_HOST') ? : '', 5 | 'post' => getenv('MAIL_PORT') ? : '', 6 | 'username' => getenv('MAIL_USERNAME') ? : '', 7 | 'password' => getenv('MAIL_PASSWORD') ? : '' 8 | ]; -------------------------------------------------------------------------------- /src/config/queue.php: -------------------------------------------------------------------------------- 1 | getenv('RABBITMQ_HOST') ? : 'localhost', 5 | 'port' => getenv('RABBITMQ_PORT') ? : '5672', 6 | 'user' => getenv('RABBITMQ_USER') ? : 'guest', 7 | 'pass' => getenv('RABBITMQ_PASS') ? : 'guest' 8 | ]; -------------------------------------------------------------------------------- /src/app/Http/Constant/EntityConstant.php: -------------------------------------------------------------------------------- 1 | 'pdo_mysql', 5 | 'host' => getenv('DB_HOSTNAME') ? : 'localhost', 6 | 'user' => getenv('DB_USERNAME') ? : 'user', 7 | 'password' => getenv('DB_PASSWORD') ? : '', 8 | 'dbname' => getenv('DB_DATANAME') ? : '', 9 | 'charset' => 'utf8' 10 | ]; -------------------------------------------------------------------------------- /src/app/Bootstrap/Component/ComponentInterface.php: -------------------------------------------------------------------------------- 1 | container = $container; 17 | } 18 | } -------------------------------------------------------------------------------- /src/app/Http/Exception/UserException.php: -------------------------------------------------------------------------------- 1 | [101, '用户不存在'], 10 | 'USERNAME_NON_EXIST_OR_PASSWORD_ERROR' => [102, '用户不存在或密码错误'] 11 | ]; 12 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/middleware.php: -------------------------------------------------------------------------------- 1 | addBodyParsingMiddleware(); 8 | // Route Middleware 9 | $app->addRoutingMiddleware(); 10 | // Cross Origin Resource Sharing Middleware 11 | $app->add(new CORSMiddleware()); 12 | }; 13 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | # App Name 使用小写 2 | APP_NAME=slim4 3 | # MySQL 4 | MYSQL_ROOT_PASSWORD=password 5 | MYSQL_DATABASE=app 6 | MYSQL_USER=app 7 | MYSQL_PASSWORD=password 8 | # Port 9 | HTTP_PORT=7000 10 | MYSQL_PORT=7001 11 | REDIS_PORT=70002 12 | PHPMYADMIN_PORT=7003 13 | # RabbitMQ 14 | RABBITMQ_MANAGEMENT_PORT=7004 15 | RABBITMQ_MQTT_PORT=7005 16 | RABBITMQ_WEB_MQTT_PORT=7006 17 | # SuperVisor 18 | SUPERVISOR_PORT=7007 19 | -------------------------------------------------------------------------------- /src/app/Bootstrap/route.php: -------------------------------------------------------------------------------- 1 | container = $container; 20 | } 21 | } -------------------------------------------------------------------------------- /src/app/Http/Service/UserService.php: -------------------------------------------------------------------------------- 1 | userModel = new UserModel($container); 17 | } 18 | } -------------------------------------------------------------------------------- /php/php.ini-development: -------------------------------------------------------------------------------- 1 | ; 错误处理 2 | error_reporting = E_ALL 3 | display_errors = On 4 | display_startup_errors = On 5 | ; 启用内存使用信息的收集 6 | mysqlnd.collect_memory_statistics = On 7 | ; 开启 PHP 信息输出 8 | expose_php = 1 9 | ; 单次请求允许上传的文件数量 10 | max_file_uploads = 10 11 | ; 允许上传的最大文件大小,还要注意 Nginx 的 client_max_body_size 配置 12 | post_max_size = 50M 13 | upload_max_filesize = 50M 14 | ; 默认时区 15 | date.timezone = Asia/Shanghai 16 | ; 最大超时时间 17 | max_execution_time = 5 18 | ; 关闭 Opcache 19 | opcache.enable = 0 20 | -------------------------------------------------------------------------------- /src/.env.example: -------------------------------------------------------------------------------- 1 | # 当前环境:dev - 开发、test - 测试、production - 生产 2 | APP_ENV=dev 3 | # 时区 4 | TIMEZONE=Asia/Shanghai 5 | # Log 6 | LOG_NAME=Logger 7 | LOG_PATH=/var/log 8 | # MySQL 9 | DB_SERVERER=db 10 | DB_USERNAME=app 11 | DB_PASSWORD=password 12 | DB_DATANAME=app 13 | # Redis 14 | REDIS_HOST=redis 15 | REDIS_PORT=6379 16 | # RabbitMQ 17 | RABBITMQ_HOST=mq 18 | RABBITMQ_PORT=5672 19 | RABBITMQ_USER=user 20 | RABBITMQ_PASS=password 21 | RABBITMQ_MQTT_PORT=1883 22 | # Mail 23 | MAIL_HOST= 24 | MAIL_PORT= 25 | MAIL_USERNAME= 26 | MAIL_PASSWORD= -------------------------------------------------------------------------------- /src/app/Http/Exception/DBException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 18 | } 19 | 20 | parent::__construct($message, $this->DBExceptionCode); 21 | } 22 | } -------------------------------------------------------------------------------- /src/config/route.yaml: -------------------------------------------------------------------------------- 1 | user: 2 | method : POST 3 | path : /user 4 | controller: Dolphin\Ting\Http\Controller\UserController:getUser 5 | userList: 6 | method : POST 7 | path : /users 8 | controller: Dolphin\Ting\Http\Controller\UserController:getUserList 9 | userSignIn: 10 | method : POST 11 | path : /signin 12 | controller: Dolphin\Ting\Http\Controller\UserController:signIn 13 | userSignUp: 14 | method : POST 15 | path : /signup 16 | controller: Dolphin\Ting\Http\Controller\UserController:signup 17 | -------------------------------------------------------------------------------- /src/public/command.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new GenerateRandomUserCommand($container)); 14 | 15 | try { 16 | $application->run(); 17 | } catch (Exception $e) { 18 | echo $e->getMessage(); 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/config/app.php: -------------------------------------------------------------------------------- 1 | getenv("APP_NAME") ? : 'Slim4-Skeleton', 5 | 'url' => getenv("APP_URL") ? : 'http://localhost', 6 | 'env' => getenv("APP_ENV") ? : 'production', 7 | // 需要框架加载的组件,不需要的直接注释 8 | 'component' => [ 9 | Dolphin\Ting\Bootstrap\Component\Logger::class, // 日志 10 | Dolphin\Ting\Bootstrap\Component\EntityManager::class, // ORM 11 | Dolphin\Ting\Bootstrap\Component\Guzzle::class, // Guzzle Http Client 12 | Dolphin\Ting\Bootstrap\Component\Cache::class, // Cache 13 | Dolphin\Ting\Bootstrap\Component\Queue::class // 消息队列 14 | ] 15 | ]; -------------------------------------------------------------------------------- /src/public/queue.php: -------------------------------------------------------------------------------- 1 | set('Guzzle', function () use ($container) { 23 | return new Client(); 24 | }); 25 | } 26 | } -------------------------------------------------------------------------------- /php/Dockerfile: -------------------------------------------------------------------------------- 1 | # Base Image 2 | FROM php:8.0.3-fpm-alpine 3 | # Install Extensions 4 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories \ 5 | && apk update \ 6 | && apk add --no-cache ${PHPIZE_DEPS} \ 7 | && pecl install redis-5.3.4 \ 8 | && docker-php-ext-install pdo_mysql opcache sockets \ 9 | && docker-php-ext-enable redis \ 10 | && apk del ${PHPIZE_DEPS} \ 11 | && apk add supervisor \ 12 | && apk add tzdata 13 | # Set Timezone 14 | ENV TZ Asia/Shanghai 15 | # Install Composer 16 | COPY --from=composer /usr/bin/composer /usr/bin/composer 17 | # Start Supervisor 18 | CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"] -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | root /var/www/html/public; 5 | index index.php; 6 | 7 | access_log /var/log/nginx/app.access.log; 8 | error_log /var/log/nginx/app.error.log; 9 | 10 | location / { 11 | try_files $uri $uri/ /index.php?$query_string; 12 | } 13 | 14 | location ~ \.php$ { 15 | fastcgi_pass php-fpm:9000; 16 | fastcgi_index index.php; 17 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 18 | include fastcgi_params; 19 | } 20 | 21 | location ~ /\.ht { 22 | deny all; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/app/Bootstrap/Component/Cache.php: -------------------------------------------------------------------------------- 1 | set('Cache', function () use ($container) { 18 | // 日志配置 19 | $config = $container->get('Config')['cache']; 20 | 21 | $cache = new Redis(); 22 | $cache->connect($config['host'], $config['port']); 23 | 24 | return $cache; 25 | }); 26 | } 27 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/component.php: -------------------------------------------------------------------------------- 1 | set('Config', function () use ($appConfig) { 10 | return [ 11 | 'app' => $appConfig, 12 | 'log' => require CONFPATH . 'log.php', 13 | 'database' => require CONFPATH . 'database.php', 14 | 'cache' => require CONFPATH . 'cache.php', 15 | 'queue' => require CONFPATH . 'queue.php', 16 | 'mail' => require CONFPATH . 'mail.php' 17 | ]; 18 | }); 19 | // 启用的组件 20 | $enableComponent = $appConfig['component']; 21 | // 注册组件 22 | foreach ($enableComponent as $componentClassName) { 23 | call_user_func([$componentClassName, 'register'], $container); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /php/php.ini-production: -------------------------------------------------------------------------------- 1 | ; 错误处理 2 | error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT 3 | display_errors = Off 4 | display_startup_errors = Off 5 | ; 禁用内存使用信息的收集 6 | mysqlnd.collect_memory_statistics = Off 7 | ; 关闭 PHP 信息输出 8 | expose_php = 0 9 | ; 单次请求允许上传的文件数量 10 | max_file_uploads = 10 11 | ; 允许上传的最大文件大小,还要注意 Nginx 的 client_max_body_size 配置 12 | post_max_size = 20M 13 | upload_max_filesize = 20M 14 | ; 默认时区 15 | date.timezone = Asia/Shanghai 16 | ; 最大超时时间 17 | max_execution_time = 5 18 | ; 开启 Opcache 19 | opcache.enable = 1 20 | ; Opcache 占用的内存大小,默认 128 m,可按实际服务器的内存来配置 21 | opcache.memory_consumption = 256 22 | ; Interned String 使用的内存大小,默认 8 m,可按实际服务器的内存来配置 23 | opcache.interned_strings_buffer = 64 24 | ; 最大脚本数,默认 10000,允许的值 200 ~ 1000000 25 | opcache.max_accelerated_files = 100000 26 | ; 关闭文件变化检查,所有代码更新后需要重启 PHP-FPM 服务:service php-fpm reload 27 | opcache.validate_timestamps = 0 28 | opcache.file_update_protection = 0 29 | ; 是否缓存注释 30 | opcache.save_comments = 0 31 | -------------------------------------------------------------------------------- /src/app/Http/Response/BusinessResponse.php: -------------------------------------------------------------------------------- 1 | addHeader('Content-Type', 'application/json'); 18 | 19 | $responseFactory = new ResponseFactory(); 20 | $response = $responseFactory->createResponse(); 21 | $responseBody = $response->getBody(); 22 | $responseBody->write(json_encode([ 23 | 'code' => $code, 24 | 'note' => $note, 25 | 'data' => $data 26 | ])); 27 | 28 | parent::__construct(StatusCodeInterface::STATUS_OK, $header, $responseBody); 29 | } 30 | } -------------------------------------------------------------------------------- /src/app/Http/Queue/UserQueue.php: -------------------------------------------------------------------------------- 1 | getBody(); 29 | // 转数组 30 | $data = json_decode($json, true); 31 | // 检查参数 32 | if (! isset($data['user_id'])) { 33 | echo 'RabbitMQ Message Error.' . PHP_EOL; 34 | 35 | return; 36 | } 37 | // 取出 UserId 38 | $userId = $data['user_id']; 39 | // 这里只是演示,没有实际的业务实现 40 | echo 'User:' . $userId . PHP_EOL; 41 | } 42 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/error.php: -------------------------------------------------------------------------------- 1 | getCallableResolver(); 20 | $responseFactory = $app->getResponseFactory(); 21 | // 异常处理 22 | $exception = new Exception($callableResolver, $responseFactory); 23 | 24 | $serverRequest = ServerRequestCreatorFactory::create(); 25 | $request = $serverRequest->createServerRequestFromGlobals(); 26 | // 程序中止时执行 27 | $error = new Error($request, $exception, $isProduction); 28 | register_shutdown_function($error); 29 | // 添加错误处理中间件 30 | $errorMiddleware = $app->addErrorMiddleware(true, false, !$isProduction); 31 | $errorMiddleware->setDefaultErrorHandler($exception); 32 | }; -------------------------------------------------------------------------------- /src/app/Http/Request/UserIdRequest.php: -------------------------------------------------------------------------------- 1 | getParsedBody(); 25 | 26 | $this->isNotNullRequestBodyParam('user_id', 'numeric'); 27 | 28 | $this->setUserId((int) $body['user_id']); 29 | } 30 | 31 | /** 32 | * @return int 33 | */ 34 | public function getUserId(): int 35 | { 36 | return $this->userId; 37 | } 38 | 39 | /** 40 | * @param int $userId 41 | */ 42 | public function setUserId(int $userId): void 43 | { 44 | $this->userId = $userId; 45 | } 46 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/app.php: -------------------------------------------------------------------------------- 1 | load(); 13 | // Set TimeZone 14 | date_default_timezone_set(getenv('TIMEZONE')); 15 | // Set Env 16 | define('ENV', getenv("ENV")); 17 | // Create Container 18 | $container = new Container(); 19 | // Set Container 20 | AppFactory::setContainer($container); 21 | // Create App 22 | $app = AppFactory::create(); 23 | // Component 24 | (require BASEPATH . DIRECTORY_SEPARATOR . 'component.php')($container); 25 | // Middleware 26 | (require BASEPATH . DIRECTORY_SEPARATOR . 'middleware.php')($app); 27 | // Route 28 | (require BASEPATH . DIRECTORY_SEPARATOR . 'route.php')($app); 29 | // Exception 30 | (require BASEPATH . DIRECTORY_SEPARATOR . 'error.php')($app); 31 | 32 | return [ 33 | 'app' => $app, 34 | 'container' => $container 35 | ]; -------------------------------------------------------------------------------- /src/app/Http/Response/UserListResponse.php: -------------------------------------------------------------------------------- 1 | $this->getTotal(), 23 | 'user' => $this->getUser() 24 | ]; 25 | } 26 | 27 | /** 28 | * @return int 29 | */ 30 | public function getTotal(): int 31 | { 32 | return $this->total; 33 | } 34 | 35 | /** 36 | * @param int $total 37 | */ 38 | public function setTotal(int $total): void 39 | { 40 | $this->total = $total; 41 | } 42 | 43 | /** 44 | * @return UserResponse[] 45 | */ 46 | public function getUser(): array 47 | { 48 | return $this->user; 49 | } 50 | 51 | /** 52 | * @param UserResponse[] $user 53 | */ 54 | public function setUser(array $user): void 55 | { 56 | $this->user = $user; 57 | } 58 | } -------------------------------------------------------------------------------- /src/LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2017-2017 dolphin.wang <416509859@qq.com> 4 | 5 | > Permission is hereby granted, free of charge, to any person obtaining a copy 6 | > of this software and associated documentation files (the "Software"), to deal 7 | > in the Software without restriction, including without limitation the rights 8 | > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | > copies of the Software, and to permit persons to whom the Software is 10 | > furnished to do so, subject to the following conditions: 11 | > 12 | > The above copyright notice and this permission notice shall be included in 13 | > all copies or substantial portions of the Software. 14 | > 15 | > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | > THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/app/Http/Response/UserResponse.php: -------------------------------------------------------------------------------- 1 | $this->getUserId(), 23 | 'username' => $this->getUsername() 24 | ]; 25 | } 26 | 27 | /** 28 | * @return int 29 | */ 30 | public function getUserId(): int 31 | { 32 | return $this->userId; 33 | } 34 | 35 | /** 36 | * @param int $userId 37 | */ 38 | public function setUserId(int $userId): void 39 | { 40 | $this->userId = $userId; 41 | } 42 | 43 | /** 44 | * @return string 45 | */ 46 | public function getUsername(): string 47 | { 48 | return $this->username; 49 | } 50 | 51 | /** 52 | * @param string $username 53 | */ 54 | public function setUsername(string $username): void 55 | { 56 | $this->username = $username; 57 | } 58 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/Error/Response.php: -------------------------------------------------------------------------------- 1 | withHeader('Access-Control-Allow-Credentials', 'true') 15 | ->withHeader('Access-Control-Allow-Origin', $origin) 16 | ->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization') 17 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, PATCH, DELETE, OPTIONS') 18 | ->withHeader('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0') 19 | ->withAddedHeader('Cache-Control', 'post-check=0, pre-check=0') 20 | ->withHeader('Pragma', 'no-cache'); 21 | 22 | if (ob_get_contents()) { 23 | ob_clean(); 24 | } 25 | 26 | parent::emit($response); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/app/Bootstrap/Error/Exception.php: -------------------------------------------------------------------------------- 1 | exception; 14 | $exceptionCode = $exception->getCode(); 15 | $exceptionMessage = $exception->getMessage(); 16 | 17 | $content = json_encode([ 18 | 'code' => $exceptionCode, 19 | 'note' => $exceptionMessage, 20 | 'data' => [] 21 | ], JSON_PRETTY_PRINT); 22 | 23 | $response = $this->responseFactory->createResponse(StatusCodeInterface::STATUS_OK); 24 | 25 | $response->getBody()->write($content); 26 | 27 | return $response->withHeader('Access-Control-Allow-Origin', '*') 28 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') 29 | ->withHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization') 30 | ->withHeader('Content-Type', 'application/json'); 31 | } 32 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/Component/EntityManager.php: -------------------------------------------------------------------------------- 1 | set('EntityManager', function () use ($container) { 25 | // 数据库配置 26 | /** @var Connection $pdoDriver */ 27 | $pdoDriver = $container->get('Config')['database']; 28 | // 是否为生产环境 29 | $isProduction = ENV === 'production'; 30 | // 创建 ORM 配置 31 | $entityManagerConfig = Setup::createAnnotationMetadataConfiguration([], ! $isProduction); 32 | // 非生产环境记录 SQL 日志 33 | if (! $isProduction) { 34 | $entityManagerConfig->setSQLLogger(new DoctrineLogger($container)); 35 | } 36 | 37 | return ORMEntityManager::create($pdoDriver, $entityManagerConfig); 38 | }); 39 | } 40 | } -------------------------------------------------------------------------------- /src/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dolphin836/slim4-api-skeleton", 3 | "description": "基于 Slim 框架的 API 脚手架。", 4 | "keywords": ["Slim", "脚手架", "MVC"], 5 | "type": "project", 6 | "homepage": "https://github.com/dolphin836/Slim4-API-Skeleton", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "dolphin.wang", 11 | "email": "wanghaibing836@gmail.com", 12 | "homepage": "https://haibing.site" 13 | } 14 | ], 15 | "require": { 16 | "php": "^8.0", 17 | "ext-json": "*", 18 | "ext-redis": "*", 19 | "slim/slim": "4.*", 20 | "vlucas/phpdotenv": "^2.4", 21 | "slim/psr7": "^1.1", 22 | "doctrine/orm": "^2.7", 23 | "php-di/php-di": "^6.2", 24 | "guzzlehttp/guzzle": "^7.0", 25 | "php-amqplib/php-amqplib": "^3.0", 26 | "php-mqtt/client": "^1.1", 27 | "monolog/monolog": "^2.1", 28 | "symfony/yaml": "^5.1", 29 | "symfony/serializer": "^5.3" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "Dolphin\\Ting\\": "app/" 34 | } 35 | }, 36 | "scripts": { 37 | "start": "php -S localhost:8080 -t public" 38 | }, 39 | "repositories": { 40 | "packagist": { 41 | "type": "composer", 42 | "url": "https://mirrors.aliyun.com/composer/" 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/app/Bootstrap/Middleware/CORSMiddleware.php: -------------------------------------------------------------------------------- 1 | getMethod(); 25 | // METHOD_OPTIONS 请求 26 | if ($method === self::METHOD_OPTIONS) { 27 | 28 | $response = new \Slim\Psr7\Response(); 29 | 30 | return $response->withStatus(204) 31 | ->withHeader('Access-Control-Allow-Origin', '*') 32 | ->withHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS') 33 | ->withHeader('Access-Control-Allow-Headers', 'Content-Type') 34 | ->withHeader('Content-Type', 'application/json'); 35 | } 36 | 37 | return $handler->handle($request); 38 | } 39 | } -------------------------------------------------------------------------------- /src/app/Http/Model/Model.php: -------------------------------------------------------------------------------- 1 | entityManager = $container->get('EntityManager'); 18 | } 19 | 20 | /** 21 | * 保存实体 22 | * 23 | * @param $entity 24 | * 25 | * @throws ORMException 26 | * @throws OptimisticLockException 27 | * 28 | * @author wanghaibing 29 | * @date 2020/10/13 14:07 30 | */ 31 | public function save ($entity) 32 | { 33 | $this->entityManager->persist($entity); 34 | $this->entityManager->flush(); 35 | } 36 | 37 | /** 38 | * 开启事务 39 | * 40 | * @author wanghaibing 41 | * @date 2020/10/13 13:57 42 | */ 43 | public function beginTransaction () 44 | { 45 | $this->entityManager->beginTransaction(); 46 | } 47 | 48 | /** 49 | * 提交事务 50 | * 51 | * @author wanghaibing 52 | * @date 2020/10/13 13:57 53 | */ 54 | public function commit () 55 | { 56 | $this->entityManager->commit(); 57 | } 58 | 59 | /** 60 | * 回滚事务 61 | * 62 | * @author wanghaibing 63 | * @date 2020/10/13 13:58 64 | */ 65 | public function rollback () 66 | { 67 | $this->entityManager->rollback(); 68 | } 69 | } -------------------------------------------------------------------------------- /src/app/Http/Request/UserRequest.php: -------------------------------------------------------------------------------- 1 | getParsedBody(); 30 | 31 | $this->isNotNullRequestBodyParam('username', 'string'); 32 | $this->isNotNullRequestBodyParam('password', 'string'); 33 | 34 | $this->setUsername($body['username']); 35 | $this->setPassword($body['password']); 36 | } 37 | 38 | /** 39 | * @return string 40 | */ 41 | public function getUsername(): string 42 | { 43 | return $this->username; 44 | } 45 | 46 | /** 47 | * @param string $username 48 | */ 49 | public function setUsername(string $username): void 50 | { 51 | $this->username = $username; 52 | } 53 | 54 | /** 55 | * @return string 56 | */ 57 | public function getPassword(): string 58 | { 59 | return $this->password; 60 | } 61 | 62 | /** 63 | * @param string $password 64 | */ 65 | public function setPassword(string $password): void 66 | { 67 | $this->password = $password; 68 | } 69 | } -------------------------------------------------------------------------------- /mysql/MySQL.sql: -------------------------------------------------------------------------------- 1 | SET time_zone = "+08:00"; 2 | 3 | /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; 4 | /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; 5 | /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; 6 | /*!40101 SET NAMES utf8mb4 */; 7 | 8 | -- 9 | -- 数据库: `app` 10 | -- 11 | 12 | -- -------------------------------------------------------- 13 | 14 | -- 15 | -- 表结构 `user` 16 | -- 17 | 18 | CREATE TABLE `user` ( 19 | `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, 20 | `username` VARCHAR(32) NOT NULL DEFAULT '' COMMENT '用户名', 21 | `password` VARCHAR(255) NOT NULL DEFAULT '' COMMENT '密码', 22 | `last_sign_in_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后登录时间', 23 | `insert_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 24 | `last_update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 25 | PRIMARY KEY (`id`), 26 | UNIQUE KEY `uk_username` (`username`) 27 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户信息表'; 28 | 29 | -- 30 | -- 表结构 `user_sign_in` 31 | -- 32 | 33 | CREATE TABLE `user_sign_in` ( 34 | `id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT, 35 | `user_id` BIGINT(20) UNSIGNED NOT NULL DEFAULT 0 COMMENT '用户', 36 | `ip_address` VARCHAR(255) NOT NULL DEFAULT '' COMMENT 'IP 地址', 37 | `sign_in_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '登录时间', 38 | `insert_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 39 | `last_update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', 40 | PRIMARY KEY (`id`), 41 | KEY `idx_user` (`user_id`) 42 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户登录记录表'; -------------------------------------------------------------------------------- /src/app/Http/Entity/UserSignIn.php: -------------------------------------------------------------------------------- 1 | id; 30 | } 31 | 32 | /** 33 | * @param int $id 34 | */ 35 | public function setId($id): void 36 | { 37 | $this->id = $id; 38 | } 39 | 40 | /** 41 | * @return int 42 | */ 43 | public function getUserId() : int 44 | { 45 | return $this->user_id; 46 | } 47 | 48 | /** 49 | * @param int $user_id 50 | */ 51 | public function setUserId($user_id): void 52 | { 53 | $this->user_id = $user_id; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getIpAddress() : string 60 | { 61 | return $this->ip_address; 62 | } 63 | 64 | /** 65 | * @param mixed $ip_address 66 | */ 67 | public function setIpAddress($ip_address): void 68 | { 69 | $this->ip_address = $ip_address; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getSignInTime() : string 76 | { 77 | return $this->sign_in_time; 78 | } 79 | 80 | /** 81 | * @param string $sign_in_time 82 | */ 83 | public function setSignInTime($sign_in_time): void 84 | { 85 | $this->sign_in_time = $sign_in_time; 86 | } 87 | } -------------------------------------------------------------------------------- /src/app/Http/Exception/CommonException.php: -------------------------------------------------------------------------------- 1 | [100, '权限错误'], 12 | 'PARAMETER_REQUIRED' => [101, '缺少必须的参数 %s'], 13 | 'PARAMETER_TYPE_ERROR' => [102, '参数 %s 不是 %s 类型'], 14 | 'PARAMETER_IS_EMPTY' => [103, '参数 %s 不能为空'], 15 | 'PARAMETER_MIN_LENGTH' => [104, '参数 %s 长度不得小于 %d'], 16 | 'PARAMETER_MAX_LENGTH' => [105, '参数 %s 长度不得大于 %d'], 17 | 'PARAMETER_MAX_VALUE' => [106, '参数 %s 不得大于 %s'], 18 | 'PARAMETER_MIN_VALUE' => [107, '参数 %s 不得小于 %s'], 19 | 'RECORD_ALREADY_EXIST' => [108, '参数 %s 等于 %s 的记录已经存在'], 20 | 'RECORD_NOT_EXIST' => [109, '记录不存在'], 21 | 'UNKNOWN_ERROR' => [110, '未知错误'] 22 | ]; 23 | 24 | private $unknownExceptionCode = 100100; 25 | private $unknownException = '未定义的错误'; 26 | 27 | public function __construct ($exceptionCode, $data = [], $message = '') 28 | { 29 | 30 | if (isset($this->exception[$exceptionCode])) { 31 | $exceptionContent = $this->exception[$exceptionCode]; 32 | 33 | $code = $this->exceptionCode . $exceptionContent[0]; 34 | 35 | if ($message === '') { 36 | if (empty($data)) { 37 | $message = $exceptionContent[1]; 38 | } else { 39 | array_unshift($data, $exceptionContent[1]); 40 | 41 | $message = call_user_func_array('sprintf', $data); 42 | } 43 | } 44 | } else { 45 | $code = $this->unknownExceptionCode; 46 | $message = $this->unknownException; 47 | } 48 | 49 | parent::__construct($message, $code); 50 | } 51 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/Component/Logger.php: -------------------------------------------------------------------------------- 1 | set('Logger', function () use ($container) { 29 | // 日志配置 30 | $config = $container->get('Config')['log']; 31 | $logger = new MonoLog($config['name']); 32 | $processor = new UidProcessor(); 33 | $logger->pushProcessor($processor); 34 | // 触发日志的最低级别 35 | $minLogLevel = ENV === 'production' ? MonoLog::ERROR : MonoLog::DEBUG; 36 | // 按月存储 37 | $logFileName = __DIR__ . '/../../..' . $config['path'] . '/' . date('Y') . '/' . date('m') . '/' . date('Ymd') . '.log'; 38 | // 初始化 39 | $logHandler = new StreamHandler($logFileName, $minLogLevel); 40 | // 日期格式 41 | $dateFormat = "Y-m-d H:i:s"; 42 | // 默认格式是 "[%datetime%] %channel%.%level_name%: %message% %context% %extra%\n" 43 | $output = "[%datetime%] %channel%.%level_name%: %message%\n"; 44 | $formatter = new LineFormatter($output, $dateFormat); 45 | $logHandler->setFormatter($formatter); 46 | 47 | $logger->pushHandler($logHandler); 48 | 49 | return $logger; 50 | }); 51 | } 52 | } -------------------------------------------------------------------------------- /src/app/Http/Entity/User.php: -------------------------------------------------------------------------------- 1 | id; 30 | } 31 | 32 | /** 33 | * @param int $id 34 | */ 35 | public function setId ($id) : void 36 | { 37 | $this->id = $id; 38 | } 39 | 40 | /** 41 | * @return string 42 | */ 43 | public function getUsername () : string 44 | { 45 | return $this->username; 46 | } 47 | 48 | /** 49 | * @param string $username 50 | */ 51 | public function setUsername ($username) : void 52 | { 53 | $this->username = $username; 54 | } 55 | 56 | /** 57 | * @return string 58 | */ 59 | public function getPassword () : string 60 | { 61 | return $this->password; 62 | } 63 | 64 | /** 65 | * @param mixed $password 66 | */ 67 | public function setPassword ($password) : void 68 | { 69 | $this->password = $password; 70 | } 71 | 72 | /** 73 | * @return string 74 | */ 75 | public function getLastSignInTime () : string 76 | { 77 | return $this->last_sign_in_time; 78 | } 79 | 80 | /** 81 | * @param string $last_sign_in_time 82 | */ 83 | public function setLastSignInTime ($last_sign_in_time) : void 84 | { 85 | $this->last_sign_in_time = $last_sign_in_time; 86 | } 87 | } -------------------------------------------------------------------------------- /src/app/Http/Command/GenerateRandomUserCommand.php: -------------------------------------------------------------------------------- 1 | container = $container; 26 | } 27 | 28 | // 命令 29 | protected static $defaultName = 'generate-random-user'; 30 | 31 | protected function configure () 32 | { 33 | $this->setDescription('Generate User.') 34 | ->addArgument('count', InputArgument::REQUIRED, 'User Count.'); 35 | } 36 | 37 | protected function execute (InputInterface $input, OutputInterface $output) 38 | { 39 | $count = $input->getArgument('count'); 40 | 41 | for ($i = 0; $i < $count; $i++) { 42 | $username = $this->generateRandomUser(); 43 | $output->writeln('Username:' . $username); 44 | sleep(1); 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | private function generateRandomUser () 51 | { 52 | $entityManager = $this->container->get('EntityManager'); 53 | 54 | $username = 'User' . date('YmdHis') . rand(100, 999); 55 | 56 | $user = new User(); 57 | $user->setUsername($username); 58 | $user->setPassword(UserConstant::DEFAULT_PASSWORD); 59 | $user->setLastSignInTime(new DateTime()); 60 | 61 | $entityManager->save($user); 62 | 63 | return $username; 64 | } 65 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/Component/DoctrineLogger.php: -------------------------------------------------------------------------------- 1 | logger = $container->get('Logger'); 39 | } 40 | 41 | /** 42 | * @param string $sql 43 | * @param array $params 44 | * @param array $types 45 | * 46 | * @author wanghaibing 47 | * @date 2020/10/13 15:02 48 | */ 49 | public function startQuery($sql, $params = [], $types = []) 50 | { 51 | // 替换参数 52 | if ($this->isType($sql)) { 53 | if (!empty($params)) { 54 | $count = count($params); 55 | 56 | while ($count > 0) { 57 | $count--; 58 | $sql = preg_replace('/\?/', array_shift($params), $sql, 1); 59 | } 60 | } 61 | // 记录日志 62 | $this->logger->info($sql); 63 | } 64 | } 65 | 66 | /** 67 | * @author wanghaibing 68 | * @date 2020/10/13 15:06 69 | */ 70 | public function stopQuery() 71 | { 72 | 73 | } 74 | 75 | /** 76 | * 判断是否为允许的类型 77 | * 78 | * @param string $sql 79 | * 80 | * @return boolean 81 | * 82 | * @author wanghaibing 83 | * @date 2020/10/13 15:41 84 | */ 85 | private function isType(string $sql): bool 86 | { 87 | foreach ($this->type as $type) { 88 | if (str_starts_with($sql, $type)) { 89 | return true; 90 | } 91 | } 92 | 93 | return false; 94 | } 95 | } -------------------------------------------------------------------------------- /src/app/Http/Controller/UserController.php: -------------------------------------------------------------------------------- 1 | userBusiness = $userBusiness; 30 | } 31 | 32 | /** 33 | * 用户详情 34 | * 35 | * @param Request $request 36 | * 37 | * @return Response 38 | * 39 | * @throws UserException 40 | * @throws CommonException 41 | */ 42 | public function getUser (Request $request) : Response 43 | { 44 | $data = $this->userBusiness->getUserById(new UserIdRequest($request)); 45 | 46 | return new BusinessResponse($data); 47 | } 48 | 49 | /** 50 | * 用户列表 51 | * 52 | * @return Response 53 | * 54 | * @throws NoResultException 55 | * @throws NonUniqueResultException 56 | */ 57 | public function getUserList () : Response 58 | { 59 | $data = $this->userBusiness->getUserList(); 60 | 61 | return new BusinessResponse($data); 62 | } 63 | 64 | /** 65 | * 用户登录 66 | * 67 | * @param Request $request 68 | * 69 | * @return Response 70 | * 71 | * @throws CommonException 72 | * @throws UserException 73 | * @throws DBException 74 | * 75 | * @author wanghaibing 76 | * @date 2020/10/13 11:50 77 | */ 78 | public function signIn (Request $request) : Response 79 | { 80 | $this->userBusiness->signIn(new UserRequest($request)); 81 | 82 | return new BusinessResponse([]); 83 | } 84 | } -------------------------------------------------------------------------------- /docker-compose.production.yml: -------------------------------------------------------------------------------- 1 | # 测试环境 2 | # MySQL 使用本地服务、开放所有端口、提供 phpMyAdmin、PHP 使用生产的配置文件 3 | # 王海兵 2020-05-24 4 | 5 | version: "3.9" 6 | 7 | volumes: 8 | data: 9 | cache: 10 | queue: 11 | 12 | services: 13 | nginx: 14 | image: nginx 15 | container_name: ${APP_NAME}_nginx 16 | restart: always 17 | volumes: 18 | - ./nginx/default.conf:/etc/nginx/conf.d/default.conf 19 | - ./nginx/log:/var/log/nginx 20 | ports: 21 | - ${HTTP_PORT}:80 22 | depends_on: 23 | - php-fpm 24 | 25 | php-fpm: 26 | image: ${APP_NAME}_php-fpm 27 | build: ./php 28 | container_name: ${APP_NAME}_php-fpm 29 | restart: always 30 | volumes: 31 | - ./src:/var/www/html 32 | - ./php/php.ini-production:/usr/local/etc/php/php.ini 33 | - ./php/supervisord.conf:/etc/supervisord.conf 34 | - ./php/supervisor.d:/etc/supervisor.d 35 | - ./php/cron:/var/spool/cron/crontabs 36 | ports: 37 | - ${SUPERVISOR_PORT}:9001 38 | depends_on: 39 | - db 40 | - redis 41 | - mq 42 | 43 | db: 44 | image: mysql 45 | container_name: ${APP_NAME}_mysql 46 | restart: always 47 | ports: 48 | - ${MYSQL_PORT}:3306 49 | environment: 50 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} 51 | MYSQL_DATABASE: ${MYSQL_DATABASE} 52 | MYSQL_USER: ${MYSQL_USER} 53 | MYSQL_PASSWORD: ${MYSQL_PASSWORD} 54 | volumes: 55 | - data:/var/lib/mysql 56 | - ./mysql/log:/var/log/mysql 57 | - ./mysql/MySQL.sql:/docker-entrypoint-initdb.d/db.sql 58 | command: 59 | - --default-time-zone=+08:00 # 设置时区 60 | - --slow_query_log=ON # 启用慢查询日志 61 | - --long_query_time=0.5 # 慢查询日志记录的阀值:500 ms 62 | - --log_queries_not_using_indexes=ON # 慢查询记录未使用索引的记录 63 | - --slow_query_log_file=/var/log/mysql/slow.log # 慢查询的文件名 64 | 65 | phpMyAdmin: 66 | image: phpmyadmin 67 | container_name: ${APP_NAME}_phpmyadmin 68 | restart: always 69 | ports: 70 | - ${PHPMYADMIN_PORT}:80 71 | environment: 72 | - PMA_ARBITRARY=1 73 | 74 | redis: 75 | image: redis 76 | restart: always 77 | container_name: ${APP_NAME}_redis 78 | command: ["redis-server", "--appendonly", "yes"] 79 | volumes: 80 | - cache:/data 81 | 82 | mq: 83 | image: ${APP_NAME}_rabbitmq 84 | build: ./rabbitmq 85 | restart: always 86 | hostname: ${APP_NAME}_mq 87 | container_name: ${APP_NAME}_mq 88 | ports: 89 | - ${RABBITMQ_MANAGEMENT_PORT}:15672 90 | - ${RABBITMQ_MQTT_PORT}:1883 91 | - ${RABBITMQ_WEB_MQTT_PORT}:15675 92 | volumes: 93 | - queue:/var/lib/rabbitmq/mnesia/rabbit@app_mq 94 | - ./rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf -------------------------------------------------------------------------------- /docker-compose.development.yml: -------------------------------------------------------------------------------- 1 | # 开发环境 2 | # MySQL 使用本地服务、开放所有端口、提供 phpMyAdmin 3 | # 王海兵 2020-05-24 4 | 5 | version: "3.9" 6 | 7 | volumes: 8 | data: 9 | cache: 10 | queue: 11 | 12 | services: 13 | nginx: 14 | image: nginx 15 | container_name: ${APP_NAME}_nginx 16 | restart: always 17 | volumes: 18 | - ./nginx/default.conf:/etc/nginx/conf.d/default.conf 19 | - ./nginx/log:/var/log/nginx 20 | ports: 21 | - ${HTTP_PORT}:80 22 | depends_on: 23 | - php-fpm 24 | 25 | php-fpm: 26 | image: ${APP_NAME}_php-fpm 27 | build: ./php 28 | container_name: ${APP_NAME}_php-fpm 29 | restart: always 30 | volumes: 31 | - ./src:/var/www/html 32 | - ./php/php.ini-development:/usr/local/etc/php/php.ini 33 | - ./php/supervisord.conf:/etc/supervisord.conf 34 | - ./php/supervisor.d:/etc/supervisor.d 35 | - ./php/cron:/var/spool/cron/crontabs 36 | ports: 37 | - ${SUPERVISOR_PORT}:9001 38 | depends_on: 39 | - db 40 | - redis 41 | - mq 42 | 43 | db: 44 | image: mysql 45 | container_name: ${APP_NAME}_mysql 46 | restart: always 47 | ports: 48 | - ${MYSQL_PORT}:3306 49 | environment: 50 | MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} 51 | MYSQL_DATABASE: ${MYSQL_DATABASE} 52 | MYSQL_USER: ${MYSQL_USER} 53 | MYSQL_PASSWORD: ${MYSQL_PASSWORD} 54 | volumes: 55 | - data:/var/lib/mysql 56 | - ./mysql/log:/var/log/mysql 57 | - ./mysql/MySQL.sql:/docker-entrypoint-initdb.d/db.sql 58 | command: 59 | - --default-time-zone=+08:00 # 设置时区 60 | - --slow_query_log=ON # 启用慢查询日志 61 | - --long_query_time=0.5 # 慢查询日志记录的阀值:500 ms 62 | - --log_queries_not_using_indexes=ON # 慢查询记录未使用索引的记录 63 | - --slow_query_log_file=/var/log/mysql/slow.log # 慢查询的文件名 64 | 65 | phpMyAdmin: 66 | image: phpmyadmin 67 | container_name: ${APP_NAME}_phpmyadmin 68 | restart: always 69 | ports: 70 | - ${PHPMYADMIN_PORT}:80 71 | environment: 72 | - PMA_ARBITRARY=1 73 | 74 | redis: 75 | image: redis 76 | restart: always 77 | container_name: ${APP_NAME}_redis 78 | ports: 79 | - ${REDIS_PORT}:6379 80 | command: ["redis-server", "--appendonly", "yes"] 81 | volumes: 82 | - cache:/data 83 | 84 | mq: 85 | image: ${APP_NAME}_rabbitmq 86 | build: ./rabbitmq 87 | restart: always 88 | hostname: ${APP_NAME}_mq 89 | container_name: ${APP_NAME}_mq 90 | ports: 91 | - ${RABBITMQ_MANAGEMENT_PORT}:15672 92 | - ${RABBITMQ_MQTT_PORT}:1883 93 | - ${RABBITMQ_WEB_MQTT_PORT}:15675 94 | volumes: 95 | - queue:/var/lib/rabbitmq/mnesia/rabbit@app_mq 96 | - ./rabbitmq/rabbitmq.conf:/etc/rabbitmq/rabbitmq.conf -------------------------------------------------------------------------------- /src/app/Http/Request/CommonRequest.php: -------------------------------------------------------------------------------- 1 | body = $request->getParsedBody(); 22 | } 23 | 24 | /** 25 | * @param string $param 26 | * @param string $paramType 27 | * @param string $message 28 | * 29 | * @throws CommonException 30 | * 31 | * @author wanghaibing 32 | * @date 2020/9/15 11:04 33 | */ 34 | public function isParamType ($param, $paramType = 'string', $message = '') 35 | { 36 | if (! call_user_func('is_' . $paramType, $this->body[$param])) { 37 | throw new CommonException('PARAMETER_TYPE_ERROR', [$param, $paramType], $message); 38 | } 39 | } 40 | 41 | /** 42 | * @param string $param 43 | * @param string $paramType 44 | * @param string $message 45 | * 46 | * @throws CommonException 47 | * 48 | * @author wanghaibing 49 | * @date 2020/9/14 17:48 50 | */ 51 | public function isNotNullRequestBodyParam ($param, $paramType = 'string', $message = '') 52 | { 53 | if (! isset($this->body[$param])) { 54 | throw new CommonException('PARAMETER_REQUIRED', [$param], $message); 55 | } 56 | 57 | $this->isParamType($param, $paramType, $message); 58 | } 59 | 60 | /** 61 | * @param string $param 62 | * @param string $paramType 63 | * @param string $message 64 | * 65 | * @throws CommonException 66 | * 67 | * @author wanghaibing 68 | * @date 2020/9/15 11:04 69 | */ 70 | public function isNotEmptyRequestBodyParam ($param, $paramType = 'string', $message = '') 71 | { 72 | $this->isNotNullRequestBodyParam($param, $paramType, $message); 73 | 74 | if (empty($this->body[$param])) { 75 | throw new CommonException('PARAMETER_IS_EMPTY', [$param], $message); 76 | } 77 | } 78 | 79 | /** 80 | * @param string $param 81 | * @param string $paramType 82 | * @param string $message 83 | * 84 | * @throws CommonException 85 | * 86 | * @author wanghaibing 87 | * @date 2020/9/15 11:04 88 | */ 89 | public function isRequestBodyParam ($param, $paramType = 'string', $message = '') 90 | { 91 | if (isset($this->body[$param])) { 92 | $this->isParamType($param, $paramType, $message); 93 | } 94 | } 95 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/Error/Error.php: -------------------------------------------------------------------------------- 1 | 'E_ERROR', 28 | E_WARNING => 'E_WARNING', 29 | E_PARSE => 'E_PARSE', 30 | E_NOTICE => 'E_NOTICE', 31 | E_CORE_ERROR => 'E_CORE_ERROR', 32 | E_CORE_WARNING => 'E_CORE_WARNING', 33 | E_COMPILE_ERROR => 'E_COMPILE_ERROR', 34 | E_COMPILE_WARNING => 'E_COMPILE_WARNING', 35 | E_USER_ERROR => 'E_USER_ERROR', 36 | E_USER_WARNING => 'E_USER_WARNING', 37 | E_USER_NOTICE => 'E_USER_NOTICE', 38 | E_STRICT => 'E_STRICT', 39 | E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', 40 | E_DEPRECATED => 'E_DEPRECATED', 41 | E_USER_DEPRECATED => 'E_USER_DEPRECATED', 42 | E_ALL => 'E_ALL' 43 | ]; 44 | 45 | /** 46 | * @param Request $request 47 | * @param Exception $exception 48 | * @param bool $isProduction 49 | */ 50 | public function __construct(Request $request, Exception $exception, bool $isProduction) 51 | { 52 | $this->request = $request; 53 | $this->exception = $exception; 54 | $this->isProduction = $isProduction; 55 | } 56 | 57 | public function __invoke() 58 | { 59 | $programErrorData = error_get_last(); 60 | 61 | if ($programErrorData) { 62 | $errorFile = $programErrorData['file']; 63 | $errorLine = $programErrorData['line']; 64 | $errorMessage = $programErrorData['message']; 65 | $errorType = $programErrorData['type']; 66 | $message = 'Server Error.'; 67 | // 非生产环境,输出详细信息 68 | if (! $this->isProduction) { 69 | $message = isset(self::ERROR_TYPE[$errorType]) ? self::ERROR_TYPE[$errorType] . ':' : ''; 70 | $message .= $errorMessage . ' On Line ' . $errorLine . ' In File ' . $errorFile; 71 | } 72 | 73 | $exception = new HttpInternalServerErrorException($this->request, $message); 74 | 75 | $httpResponse = $this->exception->__invoke( 76 | $this->request, 77 | $exception, 78 | ! $this->isProduction, 79 | false, 80 | false 81 | ); 82 | 83 | $response = new Response(); 84 | $response->respond($httpResponse); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /src/app/Http/Model/UserModel.php: -------------------------------------------------------------------------------- 1 | entityManager->createQueryBuilder() 23 | ->select('u') 24 | ->from(EntityConstant::User, 'u') 25 | ->orderBy('u.id', 'DESC') 26 | ->getQuery() 27 | ->getResult(); 28 | } 29 | 30 | /** 31 | * 查询用户记录总数 32 | * 33 | * @return integer 34 | * 35 | * @throws NoResultException 36 | * @throws NonUniqueResultException 37 | * 38 | * @author wanghaibing 39 | * @date 2020/10/13 11:23 40 | */ 41 | public function getUserTotal () 42 | { 43 | return $this->entityManager->createQueryBuilder() 44 | ->select('count(u.id)') 45 | ->from(EntityConstant::User, 'u') 46 | ->getQuery() 47 | ->getSingleScalarResult(); 48 | } 49 | 50 | /** 51 | * 根据 Id 查询用户记录 52 | * 53 | * @param integer $userId 54 | * 55 | * @return User 56 | * 57 | * @author wanghaibing 58 | * @date 2020/9/14 14:46 59 | */ 60 | public function getUserById ($userId) 61 | { 62 | /** @var User $user */ 63 | $user = $this->entityManager->getRepository(EntityConstant::User)->findOneBy(['id' => $userId]); 64 | 65 | return $user; 66 | } 67 | 68 | /** 69 | * 根据用户名和密码查询用户记录 70 | * 71 | * @param string $username 72 | * @param string $password 73 | * 74 | * @return User 75 | * 76 | * @throws NoResultException 77 | * @throws NonUniqueResultException 78 | * 79 | * @author wanghaibing 80 | * @date 2020/10/13 12:15 81 | */ 82 | public function getUserByUsernameAndPassword ($username, $password) 83 | { 84 | $param = [ 85 | 'username' => $username, 86 | 'password' => $password 87 | ]; 88 | 89 | return $this->entityManager->createQueryBuilder() 90 | ->select('u') 91 | ->from(EntityConstant::User, 'u') 92 | ->where('u.username = :username AND u.password = :password') 93 | ->setParameters($param) 94 | ->getQuery() 95 | ->getSingleResult(); 96 | } 97 | } -------------------------------------------------------------------------------- /src/app/Http/Queue/Common.php: -------------------------------------------------------------------------------- 1 | entityManager = $container->get('EntityManager'); 42 | /** @var Queue $queue */ 43 | $queue = $container->get('Queue'); 44 | // 连接 MQ 45 | $queue->connection($this->virtualHost); 46 | // 数据库心跳检测 47 | $this->keepConnection(); 48 | // 49 | $queue->receive($this->queueName, function ($message) { 50 | if ($this->isJsonMessage($message)) { 51 | $this->callback($message); 52 | } 53 | $this->ack($message); 54 | $this->exit($message); 55 | }); 56 | } catch (Exception $e) { 57 | echo 'RabbitMQ Error:' . $e->getMessage() . PHP_EOL; 58 | } 59 | } 60 | 61 | /** 62 | * 通用 ACK 方法 63 | * 64 | * @param AMQPMessage $message 65 | */ 66 | private function ack ($message) 67 | { 68 | /** @var AMQPChannel $channel */ 69 | $channel = $message->delivery_info['channel']; 70 | 71 | $channel->basic_ack($message->delivery_info['delivery_tag']); 72 | 73 | echo 'RabbitMQ Ack.' . PHP_EOL; 74 | } 75 | 76 | /** 77 | * 通用退出脚本方法 78 | * 79 | * @param AMQPMessage $message 80 | */ 81 | private function exit ($message) 82 | { 83 | if ($message->body === 'quit') { 84 | /** @var AMQPChannel $channel */ 85 | $channel = $message->delivery_info['channel']; 86 | 87 | $channel->basic_cancel($message->delivery_info['consumer_tag']); 88 | 89 | echo 'RabbitMQ Quit.' . PHP_EOL; 90 | 91 | exit(); 92 | } 93 | } 94 | 95 | /** 96 | * 数据库自动重连 97 | */ 98 | private function keepConnection () 99 | { 100 | $isConnection = $this->entityManager->getConnection()->ping(); 101 | 102 | if (! $isConnection) { 103 | $this->entityManager->getConnection()->close(); 104 | $this->entityManager->getConnection()->connect(); 105 | } 106 | } 107 | 108 | /** 109 | * 校验消息格式 110 | * 111 | * @param AMQPMessage $message 112 | * @return bool 113 | */ 114 | private function isJsonMessage ($message) 115 | { 116 | $json = $message->getBody(); 117 | // 打印消息 118 | echo 'RabbitMQ Message:' . $json . PHP_EOL; 119 | // 转数组 120 | $data = json_decode($json, true); 121 | // 非法格式 122 | if (! is_array($data) || json_last_error() !== JSON_ERROR_NONE) { 123 | echo 'RabbitMQ Message Not Json.' . PHP_EOL; 124 | 125 | return false; 126 | } 127 | 128 | return true; 129 | } 130 | 131 | /** 132 | * 定义回调方法 133 | * 134 | * @param AMQPMessage $message 135 | */ 136 | abstract public function callback ($message); 137 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Slim4-API-Skeleton 2 | 3 | Slim-Skeleton-MVC 是基于 Slim Framework 的脚手架。其主体框架来源于我 2017 年开发的一个商业项目。如果你还不了解 Slim Framework,可以从其 [官网](https://www.slimframework.com/) 了解相关信息。和 Laravel、Yii 等全能型框架相比,Micro Framework 拥有更好的性能和更大的灵活性。 4 | 5 | #### 安装方法 6 | 7 | 使用 Composer 快速创建项目 8 | 9 | ```bash 10 | composer create-project dolphin836/slim4-api-skeleton [app-name] 11 | ``` 12 | 13 | #### 配置 WEB 服务器 14 | 15 | 详细的配置方法请阅读 [Slim Documentation Web Servers](http://www.slimframework.com/docs/v4/start/web-servers.html) 16 | 17 | #### 初始化配置文件 18 | 19 | 将 .env.example 重命名为 .env,然后填写相应的信息。 20 | 21 | ```bash 22 | # 当前环境:dev - 开发、test - 测试、production - 生产 23 | ENV=dev 24 | # 时区 25 | TIMEZONE=Asia/Shanghai 26 | # 数据库服务器地址 27 | DB_SERVERER=localhost 28 | # 数据用户名 29 | DB_USERNAME=root 30 | # 数据库用户密码 31 | DB_PASSWORD=password 32 | # 数据吗名称 33 | DB_DATANAME=app 34 | ``` 35 | 36 | #### 使用方法 37 | 38 | 命令:批量生成随机用户 39 | 40 | ```bash 41 | $ php public/command.php generate-random-user 1 42 | ``` 43 | 44 | #### 开源组件 45 | 46 | - [Doctrine ORM](https://www.doctrine-project.org) 47 | - [PHP Dotenv](https://github.com/vlucas/phpdotenv) 48 | - [PHP-DI](https://php-di.org/) 49 | - [Guzzle](https://docs.guzzlephp.org/en/stable/index.html) 50 | - [PHP Amqplib](https://github.com/php-amqplib/php-amqplib) 51 | - [Monolog](https://github.com/Seldaek/monolog) 52 | - [Yaml component](https://symfony.com/components/Yaml) 53 | 54 | #### TODO 55 | 56 | - 完善介绍,标准化 57 | - 测试用例 58 | - 持续集成 59 | - 自动文档 60 | 61 | ## 环境 62 | 63 | 建议统一使用 `Docker` 搭建开发环境 64 | 65 | - `Docker` 20.10.5 66 | - `Docker-compose` 1.28.5 67 | 68 | 项目的服务依赖 69 | 70 | - `PHP` 8.0.3 71 | - `MySQL` 8.0.23 72 | - `Redis` 6.2.1 73 | - `Composer` 2.0.11 74 | 75 | ### 启动 76 | 77 | 1. 创建 `Lumen` 配置文件,并设置相应的项 78 | 79 | ```shell 80 | cp src/.env.example src/.env 81 | ``` 82 | 83 | 2. 设置日志目录权限 84 | 85 | ```shell 86 | chmod -R 777 src/storage/logs 87 | ``` 88 | 89 | 3. 创建 `Docker` 配置文件,并设置相应的项 90 | 91 | ```shell 92 | cp .env.example .env 93 | ``` 94 | 95 | 4. 初始化并启动服务 96 | 97 | ```shell 98 | docker-compose -f docker-compose.yml up -d # 创建并启动服务 99 | docker-compose -f docker-compose.yml start # 启动服务 100 | docker-compose -f docker-compose.yml stop # 停止服务 101 | docker-compose -f docker-compose.yml down # 停止并删除服务 102 | docker-compose -f docker-compose.yml restart # 重启所有服务 103 | docker-compose -f docker-compose.yml restart service_name # 重启指定服务 104 | docker-compose -f docker-compose.yml exec service_name composer update --lock # 更新 Composer 依赖 105 | ``` 106 | 107 | 实际使用中,请根据当前环境选择对应的配置文件,`docker-compose.development.yml` 开发环境,`docker-compose.test.yml` 测试环境,`docker-compose.production.yml` 生产环境 108 | 109 | 生产环境由于启用了 `Opcache`,代码更新后需要重启 `php-fpm` 服务重新加载代码 110 | 111 | 5. 安装依赖 112 | 113 | 首次安装或者 `composer.lock` 文件有更新时需要更新依赖 114 | 115 | ```shell 116 | docker compose -f docker-compose.development.yml exec php-fpm composer update --lock # Windows 117 | docker-compose -f docker-compose.development.yml exec php-fpm composer update --lock # Linux 118 | ``` 119 | 120 | 如果本地没有安装 `Composer` 又需要升级依赖包 121 | 122 | ```shell 123 | docker compose -f docker-compose.development.yml exec php-fpm composer update # Windows 124 | docker-compose -f docker-compose.development.yml exec php-fpm composer update # Linux 125 | ``` 126 | 127 | 6. 更新代码 128 | 129 | ```shell 130 | git fetch --all 131 | git reset --hard origin/master 132 | ``` 133 | 134 | 7. SuperVisor 135 | 136 | ```shell 137 | docker compose -f docker-compose.development.yml exec php-fpm /usr/bin/supervisorctl reload # 新增、修改配置文件后重新加载 138 | docker compose -f docker-compose.development.yml exec php-fpm /usr/bin/supervisorctl update 139 | ``` 140 | -------------------------------------------------------------------------------- /src/app/Http/Business/UserBusiness.php: -------------------------------------------------------------------------------- 1 | queue = $container->get('Queue'); 41 | 42 | $this->userModel = $userModel; 43 | 44 | $this->userSignInModel = $userSignInModel; 45 | } 46 | 47 | /** 48 | * @param UserIdRequest $request 49 | * 50 | * @throws UserException 51 | * 52 | * @return UserResponse 53 | * 54 | * @author wanghaibing 55 | * @date 2020/8/21 9:32 56 | */ 57 | public function getUserById (UserIdRequest $request) 58 | { 59 | $userId = $request->getUserId(); 60 | $user = $this->userModel->getUserById($userId); 61 | // 不存在 62 | if (empty($user)) { 63 | throw new UserException('USERNAME_NON_EXIST'); 64 | } 65 | 66 | $userResponse = new UserResponse(); 67 | $userResponse->setUserId($user->getId()); 68 | $userResponse->setUsername($user->getUsername()); 69 | 70 | return $userResponse; 71 | } 72 | 73 | /** 74 | * 用户列表 75 | * 76 | * @return UserListResponse 77 | * 78 | * @throws NoResultException 79 | * @throws NonUniqueResultException 80 | * 81 | * @author wanghaibing 82 | * @date 2020/9/14 14:40 83 | */ 84 | public function getUserList () 85 | { 86 | // 查询用户列表 87 | $userList = $this->userModel->getUserList(); 88 | 89 | $userArr = []; 90 | 91 | foreach ($userList as $user) { 92 | $userResponse = new UserResponse(); 93 | $userResponse->setUserId($user->getId()); 94 | $userResponse->setUsername($user->getUsername()); 95 | 96 | $userArr[] = $userResponse; 97 | } 98 | // 查询用户数量 99 | $userTotal = $this->userModel->getUserTotal(); 100 | // 设置 Response 101 | $userListResponse = new UserListResponse(); 102 | $userListResponse->setUser($userArr); 103 | $userListResponse->setTotal($userTotal); 104 | 105 | return $userListResponse; 106 | } 107 | 108 | /** 109 | * 用户登录 110 | * 111 | * @param UserRequest $userRequest 112 | * 113 | * @throws UserException 114 | * @throws DBException 115 | * @throws Exception 116 | * 117 | * @author wanghaibing 118 | * @date 2020/10/13 12:10 119 | */ 120 | public function signIn (UserRequest $userRequest) 121 | { 122 | $username = $userRequest->getUsername(); 123 | $password = $userRequest->getPassword(); 124 | // 这里为了演示,密码直接使用了明文,请不要直接在实际项目中使用 125 | try { 126 | $user = $this->userModel->getUserByUsernameAndPassword($username, $password); 127 | } catch (NoResultException $e) { 128 | throw new UserException('USERNAME_NON_EXIST_OR_PASSWORD_ERROR'); 129 | } catch (NonUniqueResultException $e) { 130 | throw new DBException($e); 131 | } 132 | // UserId 133 | $userId = $user->getId(); 134 | // 开启事务 135 | $this->userModel->beginTransaction(); 136 | 137 | try { 138 | // 更新最后登录时间 139 | $user->setLastSignInTime(date('Y-m-d H:i:s')); 140 | 141 | $this->userModel->save($user); 142 | // 添加登录记录 143 | $userSignIn = new UserSignIn(); 144 | $userSignIn->setUserId($userId); 145 | $userSignIn->setIpAddress('127.0.0.1'); 146 | $userSignIn->setSignInTime(date('Y-m-d H:i:s')); 147 | 148 | $this->userSignInModel->save($userSignIn); 149 | // 提交事务 150 | $this->userModel->commit(); 151 | } catch (Exception $e) { 152 | // 回滚事务 153 | $this->userModel->rollback(); 154 | } 155 | // 发送 MQ 消息 156 | $message = [ 157 | 'user_id' => $userId 158 | ]; 159 | 160 | $this->queue->connection(QueueConstant::VIRTUAL_HOST_DEFAULT); 161 | $this->queue->send(json_encode($message), QueueConstant::EXCHANGE_DEFAULT); 162 | } 163 | } -------------------------------------------------------------------------------- /src/app/Bootstrap/Component/Queue.php: -------------------------------------------------------------------------------- 1 | config = $app->get('Config')['queue']; 54 | } 55 | 56 | /** 57 | * Queue register. 58 | * 59 | * @param Container $container 60 | */ 61 | public static function register(Container $container) 62 | { 63 | $container->set('Queue', function () use ($container) { 64 | return new Queue($container); 65 | }); 66 | } 67 | 68 | /** 69 | * 连接 70 | * 71 | * @param string $virtualHost Virtual Host Name 72 | * @param boolean $isConfirm 是否确认回调 73 | * 74 | * @return Queue 75 | * 76 | * @throws Exception 77 | */ 78 | public function connection(string $virtualHost, bool $isConfirm): static 79 | { 80 | $this->connection = new AMQPStreamConnection( 81 | $this->config['host'], 82 | $this->config['port'], 83 | $this->config['user'], 84 | $this->config['pass'], 85 | $virtualHost, 86 | false, 87 | 'AMQPLAIN', 88 | null, 89 | 'en_US', 90 | 180, 91 | 60, 92 | null, 93 | false, 94 | 30 95 | ); 96 | 97 | if (! $this->connection->isConnected()) { 98 | throw new Exception('Connection Fail.'); 99 | } 100 | 101 | $this->isConfirm = $isConfirm; 102 | $this->channel = $this->connection->channel(); 103 | // 一个消费者一次只读取一条消息 104 | $this->channel->basic_qos(null, 1, null); 105 | // 设置为 Confirm 模式 106 | if ($this->isConfirm && $this->channel) { 107 | $this->channel->confirm_select(); 108 | } 109 | 110 | return $this; 111 | } 112 | 113 | /** 114 | * 发送消息 115 | * 116 | * @param string $json 消息 117 | * @param string $exchange 交换机 118 | * @param Closure|null $callback 成功确认回调 119 | * 120 | * @throws Exception 121 | */ 122 | public function send(string $json, string $exchange, Closure $callback = null) 123 | { 124 | $message = new AMQPMessage($json, [ 125 | 'content_type' => 'application/json', 126 | 'delivery_mode' => AMQPMessage::DELIVERY_MODE_PERSISTENT 127 | ]); 128 | // 设置回调确认 129 | if ($this->isConfirm && $this->channel) { 130 | if (is_callable($callback) && $callback instanceof Closure) { 131 | $this->channel->set_ack_handler($callback); 132 | } 133 | } 134 | 135 | try { 136 | // 等待服务端确认 137 | $this->isConfirm && $this->channel->wait_for_pending_acks(); 138 | // 发送消息 139 | $this->channel->basic_publish($message, $exchange); 140 | // 等待服务端确认 141 | $this->isConfirm && $this->channel->wait_for_pending_acks(); 142 | } catch (Exception $e) { 143 | throw new Exception($e->getMessage()); 144 | } 145 | } 146 | 147 | /** 148 | * @param string $queue 149 | * @param Closure $callback 150 | * 151 | * @throws ErrorException 152 | */ 153 | public function receive(string $queue, Closure $callback) 154 | { 155 | if (! is_callable($callback)) { 156 | $callback = function () { 157 | echo 'RabbitMQ Receive Callback Empty.:' . PHP_EOL; 158 | }; 159 | } 160 | 161 | $this->channel->basic_consume($queue, 'consumer', false, false, false, false, $callback); 162 | 163 | while (count($this->channel->callbacks)) { 164 | $this->channel->wait(); 165 | } 166 | } 167 | 168 | /** 169 | * 关闭链接 170 | * @throws Exception 171 | */ 172 | public function shutdown() 173 | { 174 | if (! $this->connection || ! $this->channel) { 175 | throw new Exception('Connection Fail.'); 176 | } 177 | 178 | $this->channel->close(); 179 | $this->connection->close(); 180 | } 181 | } -------------------------------------------------------------------------------- /php/supervisord.conf: -------------------------------------------------------------------------------- 1 | ; Sample supervisor config file. 2 | ; 3 | ; For more information on the config file, please see: 4 | ; http://supervisord.org/configuration.html 5 | ; 6 | ; Notes: 7 | ; - Shell expansion ("~" or "$HOME") is not supported. Environment 8 | ; variables can be expanded using this syntax: "%(ENV_HOME)s". 9 | ; - Quotes around values are not supported, except in the case of 10 | ; the environment= options as shown below. 11 | ; - Comments must have a leading space: "a=b ;comment" not "a=b;comment". 12 | ; - Command will be truncated if it looks like a config file comment, e.g. 13 | ; "command=bash -c 'foo ; bar'" will truncate to "command=bash -c 'foo ". 14 | ; 15 | ; Warning: 16 | ; Paths throughout this example file use /tmp because it is available on most 17 | ; systems. You will likely need to change these to locations more appropriate 18 | ; for your system. Some systems periodically delete older files in /tmp. 19 | ; Notably, if the socket file defined in the [unix_http_server] section below 20 | ; is deleted, supervisorctl will be unable to connect to supervisord. 21 | 22 | [unix_http_server] 23 | file=/run/supervisord.sock ; the path to the socket file 24 | ;chmod=0700 ; socket file mode (default 0700) 25 | ;chown=nobody:nogroup ; socket file uid:gid owner 26 | ;username=user ; default is no username (open server) 27 | ;password=123 ; default is no password (open server) 28 | 29 | ; Security Warning: 30 | ; The inet HTTP server is not enabled by default. The inet HTTP server is 31 | ; enabled by uncommenting the [inet_http_server] section below. The inet 32 | ; HTTP server is intended for use within a trusted environment only. It 33 | ; should only be bound to localhost or only accessible from within an 34 | ; isolated, trusted network. The inet HTTP server does not support any 35 | ; form of encryption. The inet HTTP server does not use authentication 36 | ; by default (see the username= and password= options to add authentication). 37 | ; Never expose the inet HTTP server to the public internet. 38 | 39 | [inet_http_server] ; inet (TCP) server disabled by default 40 | port=0.0.0.0:9001 ; ip_address:port specifier, *:port for all iface 41 | username=app ; default is no username (open server) 42 | password=password ; default is no password (open server) 43 | 44 | [supervisord] 45 | logfile=/var/log/supervisord.log ; main log file; default $CWD/supervisord.log 46 | ;logfile_maxbytes=50MB ; max main logfile bytes b4 rotation; default 50MB 47 | ;logfile_backups=10 ; # of main logfile backups; 0 means none, default 10 48 | ;loglevel=info ; log level; default info; others: debug,warn,trace 49 | pidfile=/run/supervisord.pid ; supervisord pidfile; default supervisord.pid 50 | nodaemon=true ; start in foreground if true; default false 51 | ;silent=false ; no logs to stdout if true; default false 52 | ;minfds=1024 ; min. avail startup file descriptors; default 1024 53 | ;minprocs=200 ; min. avail process descriptors;default 200 54 | ;umask=022 ; process file creation umask; default 022 55 | ;user=chrism ; setuid to this UNIX account at startup; recommended if root 56 | ;identifier=supervisor ; supervisord identifier, default is 'supervisor' 57 | ;directory=/tmp ; default is not to cd during start 58 | ;nocleanup=true ; don't clean up tempfiles at start; default false 59 | ;childlogdir=/var/log/supervisor ; 'AUTO' child log dir, default $TEMP 60 | ;environment=KEY="value" ; key value pairs to add to environment 61 | ;strip_ansi=false ; strip ansi escape codes in logs; def. false 62 | 63 | ; The rpcinterface:supervisor section must remain in the config file for 64 | ; RPC (supervisorctl/web interface) to work. Additional interfaces may be 65 | ; added by defining them in separate [rpcinterface:x] sections. 66 | 67 | [rpcinterface:supervisor] 68 | supervisor.rpcinterface_factory = supervisor.rpcinterface:make_main_rpcinterface 69 | 70 | ; The supervisorctl section configures how supervisorctl will connect to 71 | ; supervisord. configure it match the settings in either the unix_http_server 72 | ; or inet_http_server section. 73 | 74 | [supervisorctl] 75 | serverurl=unix:///run/supervisord.sock ; use a unix:// URL for a unix socket 76 | ;serverurl=http://127.0.0.1:9001 ; use an http:// url to specify an inet socket 77 | ;username=chris ; should be same as in [*_http_server] if set 78 | ;password=123 ; should be same as in [*_http_server] if set 79 | ;prompt=mysupervisor ; cmd line prompt (default "supervisor") 80 | ;history_file=~/.sc_history ; use readline history if available 81 | 82 | ; The sample program section below shows all possible program subsection values. 83 | ; Create one or more 'real' program: sections to be able to control them under 84 | ; supervisor. 85 | 86 | ;[program:theprogramname] 87 | ;command=/bin/cat ; the program (relative uses PATH, can take args) 88 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 89 | ;numprocs=1 ; number of processes copies to start (def 1) 90 | ;directory=/tmp ; directory to cwd to before exec (def no cwd) 91 | ;umask=022 ; umask for process (default None) 92 | ;priority=999 ; the relative start priority (default 999) 93 | ;autostart=true ; start at supervisord start (default: true) 94 | ;startsecs=1 ; # of secs prog must stay up to be running (def. 1) 95 | ;startretries=3 ; max # of serial start failures when starting (default 3) 96 | ;autorestart=unexpected ; when to restart if exited after running (def: unexpected) 97 | ;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0) 98 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 99 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 100 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 101 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 102 | ;user=chrism ; setuid to this UNIX account to run the program 103 | ;redirect_stderr=true ; redirect proc stderr to stdout (default false) 104 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO 105 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 106 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) 107 | ;stdout_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 108 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 109 | ;stdout_syslog=false ; send stdout to syslog with process name (default false) 110 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 111 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 112 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) 113 | ;stderr_capture_maxbytes=1MB ; number of bytes in 'capturemode' (default 0) 114 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 115 | ;stderr_syslog=false ; send stderr to syslog with process name (default false) 116 | ;environment=A="1",B="2" ; process environment additions (def no adds) 117 | ;serverurl=AUTO ; override serverurl computation (childutils) 118 | 119 | ; The sample eventlistener section below shows all possible eventlistener 120 | ; subsection values. Create one or more 'real' eventlistener: sections to be 121 | ; able to handle event notifications sent by supervisord. 122 | 123 | ;[eventlistener:theeventlistenername] 124 | ;command=/bin/eventlistener ; the program (relative uses PATH, can take args) 125 | ;process_name=%(program_name)s ; process_name expr (default %(program_name)s) 126 | ;numprocs=1 ; number of processes copies to start (def 1) 127 | ;events=EVENT ; event notif. types to subscribe to (req'd) 128 | ;buffer_size=10 ; event buffer queue size (default 10) 129 | ;directory=/tmp ; directory to cwd to before exec (def no cwd) 130 | ;umask=022 ; umask for process (default None) 131 | ;priority=-1 ; the relative start priority (default -1) 132 | ;autostart=true ; start at supervisord start (default: true) 133 | ;startsecs=1 ; # of secs prog must stay up to be running (def. 1) 134 | ;startretries=3 ; max # of serial start failures when starting (default 3) 135 | ;autorestart=unexpected ; autorestart if exited after running (def: unexpected) 136 | ;exitcodes=0 ; 'expected' exit codes used with autorestart (default 0) 137 | ;stopsignal=QUIT ; signal used to kill process (default TERM) 138 | ;stopwaitsecs=10 ; max num secs to wait b4 SIGKILL (default 10) 139 | ;stopasgroup=false ; send stop signal to the UNIX process group (default false) 140 | ;killasgroup=false ; SIGKILL the UNIX process group (def false) 141 | ;user=chrism ; setuid to this UNIX account to run the program 142 | ;redirect_stderr=false ; redirect_stderr=true is not allowed for eventlisteners 143 | ;stdout_logfile=/a/path ; stdout log path, NONE for none; default AUTO 144 | ;stdout_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 145 | ;stdout_logfile_backups=10 ; # of stdout logfile backups (0 means none, default 10) 146 | ;stdout_events_enabled=false ; emit events on stdout writes (default false) 147 | ;stdout_syslog=false ; send stdout to syslog with process name (default false) 148 | ;stderr_logfile=/a/path ; stderr log path, NONE for none; default AUTO 149 | ;stderr_logfile_maxbytes=1MB ; max # logfile bytes b4 rotation (default 50MB) 150 | ;stderr_logfile_backups=10 ; # of stderr logfile backups (0 means none, default 10) 151 | ;stderr_events_enabled=false ; emit events on stderr writes (default false) 152 | ;stderr_syslog=false ; send stderr to syslog with process name (default false) 153 | ;environment=A="1",B="2" ; process environment additions 154 | ;serverurl=AUTO ; override serverurl computation (childutils) 155 | 156 | ; The sample group section below shows all possible group values. Create one 157 | ; or more 'real' group: sections to create "heterogeneous" process groups. 158 | 159 | ;[group:thegroupname] 160 | ;programs=progname1,progname2 ; each refers to 'x' in [program:x] definitions 161 | ;priority=999 ; the relative start priority (default 999) 162 | 163 | ; The [include] section can just contain the "files" setting. This 164 | ; setting can list multiple files (separated by whitespace or 165 | ; newlines). It can also contain wildcards. The filenames are 166 | ; interpreted as relative to this file. Included files *cannot* 167 | ; include files themselves. 168 | 169 | [include] 170 | files = /etc/supervisor.d/*.ini 171 | --------------------------------------------------------------------------------