From 84e4711f369d97b21c72aae5b3fa1ddd7cb66592 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9D=8E=E4=B8=9C=E4=BA=91?= Date: Tue, 21 Mar 2023 17:47:05 +0800 Subject: [PATCH] =?UTF-8?q?feat(installer):=20=E5=88=9D=E5=A7=8B=E5=8C=96?= =?UTF-8?q?=E5=AE=89=E8=A3=85=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- composer.json | 8 +- installer/OptionalPackages.php | 552 ++++++++++++++++++ installer/Script.php | 33 ++ installer/config.php | 314 ++++++++++ installer/resources/amqp/amqp.php | 40 ++ .../async_queue/AsyncQueueConsumer.php | 20 + .../async_queue/QueueHandleListener.php | 73 +++ .../resources/async_queue/async_queue.php | 27 + installer/resources/bin/hyperf.stub | 24 + .../resources/config_center/config_acm.php | 31 + .../resources/config_center/config_apollo.php | 36 ++ .../resources/config_center/config_etcd.php | 32 + .../resources/config_center/config_nacos.php | 39 ++ installer/resources/config_center/etcd.php | 18 + installer/resources/config_center/nacos.php | 24 + .../resources/constants/BusinessException.php | 28 + installer/resources/constants/ErrorCode.php | 24 + installer/resources/database/databases.php | 41 ++ installer/resources/database/redis.php | 27 + installer/resources/jsonrpc/services.php | 29 + installer/resources/model_cache/Model.php | 21 + installer/resources/model_cache/databases.php | 49 ++ installer/resources/tracer/opentracing.php | 49 ++ 23 files changed, 1538 insertions(+), 1 deletion(-) create mode 100755 installer/OptionalPackages.php create mode 100755 installer/Script.php create mode 100755 installer/config.php create mode 100755 installer/resources/amqp/amqp.php create mode 100755 installer/resources/async_queue/AsyncQueueConsumer.php create mode 100755 installer/resources/async_queue/QueueHandleListener.php create mode 100755 installer/resources/async_queue/async_queue.php create mode 100755 installer/resources/bin/hyperf.stub create mode 100755 installer/resources/config_center/config_acm.php create mode 100755 installer/resources/config_center/config_apollo.php create mode 100755 installer/resources/config_center/config_etcd.php create mode 100755 installer/resources/config_center/config_nacos.php create mode 100755 installer/resources/config_center/etcd.php create mode 100755 installer/resources/config_center/nacos.php create mode 100755 installer/resources/constants/BusinessException.php create mode 100755 installer/resources/constants/ErrorCode.php create mode 100755 installer/resources/database/databases.php create mode 100755 installer/resources/database/redis.php create mode 100755 installer/resources/jsonrpc/services.php create mode 100755 installer/resources/model_cache/Model.php create mode 100755 installer/resources/model_cache/databases.php create mode 100755 installer/resources/tracer/opentracing.php diff --git a/composer.json b/composer.json index 5a24e7a..c38770f 100755 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ }, "autoload": { "psr-4": { - "App\\": "app/" + "App\\": "app/", + "Installer\\": "installer/" } }, "autoload-dev": { @@ -65,9 +66,14 @@ "secure-http": false }, "scripts": { + "pre-install-cmd": "Installer\\Script::install", + "pre-update-cmd": "Installer\\Script::install", "post-root-package-install": [ "@php -r \"file_exists('.env') || copy('.env.example', '.env');\"" ], + "post-autoload-dump": [ + "rm -rf runtime/container" + ], "test": [ "rm -rf runtime", "Composer\\Config::disableProcessTimeout", diff --git a/installer/OptionalPackages.php b/installer/OptionalPackages.php new file mode 100755 index 0000000..71f2719 --- /dev/null +++ b/installer/OptionalPackages.php @@ -0,0 +1,552 @@ +[^:]+\/[^:]+)([:]*)(?P.*)$/'; + + /** + * @var IOInterface + */ + public IOInterface $io; + + /** + * Assets to remove during cleanup. + * + * @var string[] + */ + private array $assetsToRemove = [ + '.travis.yml', + ]; + + /** + * @var array + */ + private array $config; + + /** + * @var Composer + */ + private Composer $composer; + + /** + * @var array + */ + private array $composerDefinition; + + /** + * @var JsonFile + */ + private JsonFile $composerJson; + + /** + * @var Link[] + */ + private array $composerRequires; + + /** + * @var Link[] + */ + private array $composerDevRequires; + + /** + * @var string[] Dev dependencies to remove after install is complete + */ + private array $devDependencies = [ + 'composer/composer', + ]; + + /** + * @var string path to this file + */ + private string $installerSource; + + /** + * @var string + */ + private string $projectRoot; + + /** + * @var RootPackageInterface + */ + private RootPackageInterface $rootPackage; + + /** + * @var int[] + */ + private array $stabilityFlags; + + public function __construct(IOInterface $io, Composer $composer, string $projectRoot = null) + { + $this->io = $io; + $this->composer = $composer; + // Get composer.json location + $composerFile = Factory::getComposerFile(); + // Calculate project root from composer.json, if necessary + $this->projectRoot = $projectRoot ?: realpath(dirname($composerFile)); + $this->projectRoot = rtrim($this->projectRoot, '/\\') . '/'; + + // Parse the composer.json + $this->parseComposerDefinition($composer, $composerFile); + // Get optional packages configuration + $this->config = require __DIR__ . '/config.php'; + // Source path for this file + $this->installerSource = realpath(__DIR__) . '/'; + } + + /** + * Parses the composer file and populates internal data. + */ + private function parseComposerDefinition(Composer $composer, string $composerFile): void + { + $this->composerJson = new JsonFile($composerFile); + $this->composerDefinition = $this->composerJson->read(); + // Get root package or root alias package + $this->rootPackage = $composer->getPackage(); + // Get required packages + $this->composerRequires = $this->rootPackage->getRequires(); + $this->composerDevRequires = $this->rootPackage->getDevRequires(); + // Get stability flags + $this->stabilityFlags = $this->rootPackage->getStabilityFlags(); + } + + public function installHyperfScript(): void + { + $ask[] = "\n What time zone do you want to set up ?\n"; + $ask[] = " [n] Default time zone for php.ini\n"; + $ask[] = "Make your selection or type a time zone name, like Asia/Shanghai (n):\n"; + $answer = $this->io->askAndValidate( + implode('', $ask), + function ($value) { + if ($value === 'y' || $value === 'yes') { + throw new \InvalidArgumentException( + 'You should type a time zone name, like Asia/Shanghai. Or type n to skip.' + ); + } + + return trim($value); + }, + null, + 'n' + ); + + if ($answer != 'n') { + $content = file_get_contents($this->installerSource . '/resources/bin/hyperf.stub'); + $content = str_replace('%TIME_ZONE%', $answer, $content); + file_put_contents($this->projectRoot . '/bin/hyperf.php', $content); + } + } + + public function setupProject() + { + $ask[] = "\n 这个项目的项目名是什么?\n"; + $ask[] = "必填项,类似 LuxStudio:\n"; + $project_name = $this->io->askAndValidate( + implode('', $ask), + function ($value) { + $pattern = '/^\s*Lux[A-Z][A-Za-z]+\s*$/'; + preg_match($pattern, $value, $matches); + if (count($matches) <= 0) { + throw new \InvalidArgumentException('项目名非法!合法格式为:/Lux[A-Z][A-Za-z]+/'); + } + + return trim($matches[0]); + }, + null, + '' + ); + + // 替换项目名 + // composer.json + $content = file_get_contents($this->projectRoot . 'composer.json'); + $pattern = '/[A-Z][a-z]*/'; + preg_match_all($pattern, $project_name, $matches); + $composer_project_name = array_reduce($matches[0], function ($prevent, $current){ + if (is_null($prevent)) { + return strtolower($current); + } + return sprintf('%s-%s', $prevent, strtolower($current)); + }); + $composer_project_name = sprintf('web-service/%s', $composer_project_name); + $content = preg_replace('/"name": "(.*)?"/',sprintf('"name": "%s"', $composer_project_name), $content); + file_put_contents($this->projectRoot . 'composer.json', json_encode($content)); + } + + /** + * Create data and cache directories, if not present. + * + * Also sets up appropriate permissions. + */ + public function setupRuntimeDir(): void + { + $this->io->write('初始化缓存目录'); + $runtimeDir = $this->projectRoot . '/runtime'; + + if (!is_dir($runtimeDir)) { + mkdir($runtimeDir, 0775, true); + chmod($runtimeDir, 0775); + } + } + + /** + * Cleanup development dependencies. + * + * The dev dependencies should be removed from the stability flags, + * require-dev and the composer file. + */ + public function removeDevDependencies(): void + { + $this->io->write('移除安装器依赖'); + foreach ($this->devDependencies as $devDependency) { + unset($this->stabilityFlags[$devDependency], $this->composerDevRequires[$devDependency], $this->composerDefinition['require-dev'][$devDependency]); + } + } + + /** + * Prompt for each optional installation package. + * + * @codeCoverageIgnore + */ + public function promptForOptionalPackages(): void + { + foreach ($this->config['questions'] as $questionName => $question) { + $this->promptForOptionalPackage($questionName, $question); + } + } + + /** + * Prompt for a single optional installation package. + * + * @param string $questionName Name of question + * @param array $question Question details from configuration + */ + public function promptForOptionalPackage(string $questionName, array $question): void + { + $defaultOption = $question['default'] ?? 1; + if (isset($this->composerDefinition['extra']['optional-packages'][$questionName])) { + // Skip question, it's already answered + return; + } + // Get answer + $answer = $this->askQuestion($question, $defaultOption); + // Process answer + $this->processAnswer($question, $answer); + // Save user selected option + $this->composerDefinition['extra']['optional-packages'][$questionName] = $answer; + // Update composer definition + $this->composerJson->write($this->composerDefinition); + } + + /** + * Prepare and ask questions and return the answer. + * + * @param int|string $defaultOption + * @return bool|int|string + * @codeCoverageIgnore + */ + private function askQuestion(array $question, $defaultOption) + { + // Construct question + $ask = [ + sprintf("\n %s\n", $question['question']), + ]; + $defaultText = $defaultOption; + foreach ($question['options'] as $key => $option) { + $defaultText = ($key === $defaultOption) ? $option['name'] : $defaultText; + $ask[] = sprintf(" [%s] %s\n", $key, $option['name']); + } + if ($question['required'] !== true) { + $ask[] = " [n] None of the above\n"; + } + $ask[] = ($question['custom-package'] === true) + ? sprintf( + ' Make your selection or type a composer package name and version (%s): ', + $defaultText + ) + : sprintf(' Make your selection (%s): ', $defaultText); + while (true) { + // Ask for user input + $answer = $this->io->ask(implode($ask), (string)$defaultOption); + // Handle none of the options + if ($answer === 'n' && $question['required'] !== true) { + return 'n'; + } + // Handle numeric options + if (is_numeric($answer) && isset($question['options'][(int)$answer])) { + return (int)$answer; + } + // Handle string options + if (isset($question['options'][$answer])) { + return $answer; + } + // Search for package + if ($question['custom-package'] === true && preg_match(self::PACKAGE_REGEX, $answer, $match)) { + $packageName = $match['name']; + $packageVersion = $match['version']; + if (!$packageVersion) { + $this->io->write('No package version specified'); + continue; + } + $this->io->write(sprintf(' - Searching for %s:%s', $packageName, $packageVersion)); + $optionalPackage = $this->composer->getRepositoryManager()->findPackage($packageName, $packageVersion); + if ($optionalPackage === null) { + $this->io->write(sprintf('Package not found %s:%s', $packageName, $packageVersion)); + continue; + } + return sprintf('%s:%s', $packageName, $packageVersion); + } + $this->io->write('Invalid answer'); + } + return false; + } + + /** + * Process the answer of a question. + * + * @param bool|int|string $answer + */ + public function processAnswer(array $question, $answer): bool + { + if (isset($question['options'][$answer])) { + // Add packages to install + if (isset($question['options'][$answer]['packages'])) { + foreach ($question['options'][$answer]['packages'] as $packageName) { + $packageData = $this->config['packages'][$packageName]; + $this->addPackage($packageName, $packageData['version'], $packageData['whitelist'] ?? []); + } + } + // Copy files + if (isset($question['options'][$answer])) { + $force = !empty($question['force']); + foreach ($question['options'][$answer]['resources'] as $resource => $target) { + $this->copyResource($resource, $target, $force); + } + } + return true; + } + if ($question['custom-package'] === true && preg_match(self::PACKAGE_REGEX, (string)$answer, $match)) { + $this->addPackage($match['name'], $match['version'], []); + if (isset($question['custom-package-warning'])) { + $this->io->write(sprintf(' %s', $question['custom-package-warning'])); + } + return true; + } + return false; + } + + /** + * Add a package. + */ + public function addPackage(string $packageName, string $packageVersion, array $whitelist = []): void + { + $this->io->write( + sprintf( + ' - Adding package %s (%s)', + $packageName, + $packageVersion + ) + ); + // Get the version constraint + $versionParser = new VersionParser(); + $constraint = $versionParser->parseConstraints($packageVersion); + // Create package link + $link = new Link('__root__', $packageName, $constraint, 'requires', $packageVersion); + // Add package to the root package and composer.json requirements + if (in_array($packageName, $this->config['require-dev'], true)) { + unset($this->composerDefinition['require'][$packageName], $this->composerRequires[$packageName]); + + $this->composerDefinition['require-dev'][$packageName] = $packageVersion; + $this->composerDevRequires[$packageName] = $link; + } else { + unset($this->composerDefinition['require-dev'][$packageName], $this->composerDevRequires[$packageName]); + + $this->composerDefinition['require'][$packageName] = $packageVersion; + $this->composerRequires[$packageName] = $link; + } + // Set package stability if needed + switch (VersionParser::parseStability($packageVersion)) { + case 'dev': + $this->stabilityFlags[$packageName] = BasePackage::STABILITY_DEV; + break; + case 'alpha': + $this->stabilityFlags[$packageName] = BasePackage::STABILITY_ALPHA; + break; + case 'beta': + $this->stabilityFlags[$packageName] = BasePackage::STABILITY_BETA; + break; + case 'RC': + $this->stabilityFlags[$packageName] = BasePackage::STABILITY_RC; + break; + } + // Whitelist packages for the component installer + foreach ($whitelist as $package) { + if (!in_array($package, $this->composerDefinition['extra']['zf']['component-whitelist'], true)) { + $this->composerDefinition['extra']['zf']['component-whitelist'][] = $package; + $this->io->write(sprintf(' - Whitelist package %s', $package)); + } + } + } + + /** + * Copy a file to its final destination in the skeleton. + * + * @param string $resource resource file + * @param string $target destination + * @param bool $force whether or not to copy over an existing file + */ + public function copyResource(string $resource, string $target, bool $force = false): void + { + // Copy file + if ($force === false && is_file($this->projectRoot . $target)) { + return; + } + $destinationPath = dirname($this->projectRoot . $target); + if (!is_dir($destinationPath)) { + mkdir($destinationPath, 0775, true); + } + $this->io->write(sprintf(' - Copying %s', $target)); + copy($this->installerSource . $resource, $this->projectRoot . $target); + } + + /** + * Update the root package based on current state. + */ + public function updateRootPackage(): void + { + $this->rootPackage->setRequires($this->composerRequires); + $this->rootPackage->setDevRequires($this->composerDevRequires); + $this->rootPackage->setStabilityFlags($this->stabilityFlags); + $this->rootPackage->setAutoload($this->composerDefinition['autoload']); + $this->rootPackage->setDevAutoload($this->composerDefinition['autoload-dev']); + $this->rootPackage->setExtra($this->composerDefinition['extra'] ?? []); + } + + /** + * Remove the installer from the composer definition. + */ + public function removeInstallerFromDefinition(): void + { + $this->io->write('Remove installer'); + // Remove installer script autoloading rules + unset( + $this->composerDefinition['autoload']['psr-4']['Installer\\'], + $this->composerDefinition['autoload-dev']['psr-4']['InstallerTest\\'], + $this->composerDefinition['extra']['branch-alias'], + $this->composerDefinition['extra']['optional-packages'], + $this->composerDefinition['scripts']['pre-update-cmd'], + $this->composerDefinition['scripts']['pre-install-cmd'] + ); + } + + /** + * Finalize the package. + * + * Writes the current JSON state to composer.json, clears the + * composer.lock file, and cleans up all files specific to the + * installer. + * + * @codeCoverageIgnore + */ + public function finalizePackage(): void + { + // Update composer definition + $this->composerJson->write($this->composerDefinition); + $this->clearComposerLockFile(); + $this->cleanUp(); + } + + /** + * Removes composer.lock file from gitignore. + * + * @codeCoverageIgnore + */ + private function clearComposerLockFile(): void + { + $this->io->write('Removing composer.lock from .gitignore'); + $ignoreFile = sprintf('%s/.gitignore', $this->projectRoot); + $content = $this->removeLinesContainingStrings(['composer.lock'], file_get_contents($ignoreFile)); + file_put_contents($ignoreFile, $content); + } + + /** + * Clean up/remove installer classes and assets. + * + * On completion of install/update, removes the installer classes (including + * this one) and assets (including configuration and templates). + * + * @codeCoverageIgnore + */ + private function cleanUp(): void + { + $this->io->write('Removing Expressive installer classes, configuration, tests and docs'); + foreach ($this->assetsToRemove as $target) { + $target = $this->projectRoot . $target; + if (file_exists($target)) { + unlink($target); + } + } + $this->recursiveRmdir($this->installerSource); + } + + /** + * Remove lines from string content containing words in array. + */ + public function removeLinesContainingStrings(array $entries, string $content): string + { + $entries = implode( + '|', + array_map(function ($word) { + return preg_quote($word, '/'); + }, $entries) + ); + return preg_replace('/^.*(?:' . $entries . ").*$(?:\r?\n)?/m", '', $content); + } + + /** + * Recursively remove a directory. + * + * @codeCoverageIgnore + */ + private function recursiveRmdir(string $directory): void + { + if (!is_dir($directory)) { + return; + } + $rdi = new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS); + $rii = new RecursiveIteratorIterator($rdi, RecursiveIteratorIterator::CHILD_FIRST); + foreach ($rii as $filename => $fileInfo) { + if ($fileInfo->isDir()) { + rmdir($filename); + continue; + } + unlink($filename); + } + rmdir($directory); + } +} diff --git a/installer/Script.php b/installer/Script.php new file mode 100755 index 0000000..2851eff --- /dev/null +++ b/installer/Script.php @@ -0,0 +1,33 @@ +getIO(), $event->getComposer()); + + $installer->io->write('设置可选包'); + + $installer->setupRuntimeDir(); + $installer->removeDevDependencies(); + $installer->setupProject();die; + // $installer->installHyperfScript(); + // $installer->promptForOptionalPackages(); + // $installer->updateRootPackage(); + // $installer->removeInstallerFromDefinition(); + // $installer->finalizePackage(); + } +} diff --git a/installer/config.php b/installer/config.php new file mode 100755 index 0000000..aecca42 --- /dev/null +++ b/installer/config.php @@ -0,0 +1,314 @@ + [ + 'hyperf/amqp' => [ + 'version' => '~3.0.0', + ], + 'hyperf/async-queue' => [ + 'version' => '~3.0.0', + ], + 'hyperf/database' => [ + 'version' => '~3.0.0', + ], + 'hyperf/db-connection' => [ + 'version' => '~3.0.0', + ], + 'hyperf/model-cache' => [ + 'version' => '~3.0.0', + ], + 'hyperf/constants' => [ + 'version' => '~3.0.0', + ], + 'hyperf/json-rpc' => [ + 'version' => '~3.0.0', + ], + 'hyperf/redis' => [ + 'version' => '~3.0.0', + ], + 'hyperf/rpc' => [ + 'version' => '~3.0.0', + ], + 'hyperf/rpc-client' => [ + 'version' => '~3.0.0', + ], + 'hyperf/rpc-server' => [ + 'version' => '~3.0.0', + ], + 'hyperf/grpc-client' => [ + 'version' => '~3.0.0', + ], + 'hyperf/grpc-server' => [ + 'version' => '~3.0.0', + ], + 'hyperf/elasticsearch' => [ + 'version' => '~3.0.0', + ], + 'hyperf/config-apollo' => [ + 'version' => '~3.0.0', + ], + 'hyperf/config-aliyun-acm' => [ + 'version' => '~3.0.0', + ], + 'hyperf/config-etcd' => [ + 'version' => '~3.0.0', + ], + 'hyperf/config-nacos' => [ + 'version' => '~3.0.0', + ], + 'hyperf/tracer' => [ + 'version' => '~3.0.0', + ], + 'hyperf/service-governance' => [ + 'version' => '~3.0.0', + ], + ], + 'require-dev' => [ + ], + 'questions' => [ + 'database' => [ + 'question' => 'Do you want to use Database (MySQL Client) ?', + 'default' => 'y', + 'required' => false, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/database', + 'hyperf/db-connection', + ], + 'resources' => [ + 'resources/database/databases.php' => 'config/autoload/databases.php', + ], + ], + ], + ], + 'redis' => [ + 'question' => 'Do you want to use Redis Client ?', + 'default' => 'y', + 'required' => false, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/redis', + ], + 'resources' => [ + 'resources/database/redis.php' => 'config/autoload/redis.php', + ], + ], + ], + ], + 'rpc' => [ + 'question' => 'Which RPC protocol do you want to use ?', + 'default' => 'n', + 'required' => false, + 'custom-package' => true, + 'options' => [ + 1 => [ + 'name' => 'JSON RPC with Service Governance', + 'packages' => [ + 'hyperf/json-rpc', + 'hyperf/rpc', + 'hyperf/rpc-client', + 'hyperf/rpc-server', + 'hyperf/service-governance', + ], + 'resources' => [ + ], + ], + 2 => [ + 'name' => 'JSON RPC', + 'packages' => [ + 'hyperf/json-rpc', + 'hyperf/rpc', + 'hyperf/rpc-client', + 'hyperf/rpc-server', + ], + 'resources' => [ + 'resources/jsonrpc/services.php' => 'config/autoload/services.php', + ], + ], + 3 => [ + 'name' => 'gRPC', + 'packages' => [ + 'hyperf/grpc-client', + 'hyperf/grpc-server', + ], + 'resources' => [ + ], + ], + ], + ], + 'config-center' => [ + 'question' => 'Which config center do you want to use ?', + 'default' => 'n', + 'required' => false, + 'custom-package' => true, + 'options' => [ + 1 => [ + 'name' => 'Apollo', + 'packages' => [ + 'hyperf/config-apollo', + ], + 'resources' => [ + 'resources/config_center/config_apollo.php' => 'config/autoload/config_center.php', + ], + ], + 2 => [ + 'name' => 'Aliyun ACM', + 'packages' => [ + 'hyperf/config-aliyun-acm', + ], + 'resources' => [ + 'resources/config_center/config_acm.php' => 'config/autoload/config_center.php', + ], + ], + 3 => [ + 'name' => 'ETCD', + 'packages' => [ + 'hyperf/config-etcd', + ], + 'resources' => [ + 'resources/config_center/etcd.php' => 'config/autoload/etcd.php', + 'resources/config_center/config_etcd.php' => 'config/autoload/config_center.php', + ], + ], + 4 => [ + 'name' => 'Nacos', + 'packages' => [ + 'hyperf/config-nacos', + ], + 'resources' => [ + 'resources/config_center/nacos.php' => 'config/autoload/nacos.php', + 'resources/config_center/config_nacos.php' => 'config/autoload/config_center.php', + ], + ], + ], + ], + 'constants' => [ + 'question' => 'Do you want to use hyperf/constants component ?', + 'default' => 'n', + 'required' => false, + 'force' => true, + 'custom-package' => false, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/constants', + ], + 'resources' => [ + 'resources/constants/ErrorCode.php' => 'app/Constants/ErrorCode.php', + 'resources/constants/BusinessException.php' => 'app/Exception/BusinessException.php', + ], + ], + ], + ], + 'async-queue' => [ + 'question' => 'Do you want to use hyperf/async-queue component ? (A simple redis queue component)', + 'default' => 'n', + 'required' => false, + 'force' => true, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/async-queue', + ], + 'resources' => [ + 'resources/async_queue/async_queue.php' => 'config/autoload/async_queue.php', + 'resources/async_queue/AsyncQueueConsumer.php' => 'app/Process/AsyncQueueConsumer.php', + 'resources/async_queue/QueueHandleListener.php' => 'app/Listener/QueueHandleListener.php', + 'resources/database/redis.php' => 'config/autoload/redis.php', + ], + ], + ], + ], + 'amqp' => [ + 'question' => 'Do you want to use hyperf/amqp component ?', + 'default' => 'n', + 'required' => false, + 'force' => true, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/amqp', + ], + 'resources' => [ + 'resources/amqp/amqp.php' => 'config/autoload/amqp.php', + ], + ], + ], + ], + 'model-cache' => [ + 'question' => 'Do you want to use hyperf/model-cache component ?', + 'default' => 'n', + 'required' => false, + 'force' => true, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/model-cache', + ], + 'resources' => [ + 'resources/model_cache/Model.php' => 'app/Model/Model.php', + 'resources/model_cache/databases.php' => 'config/autoload/databases.php', + 'resources/database/redis.php' => 'config/autoload/redis.php', + ], + ], + ], + ], + 'elasticsearch' => [ + 'question' => 'Do you want to use hyperf/elasticsearch component ?', + 'default' => 'n', + 'required' => false, + 'force' => true, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/elasticsearch', + ], + 'resources' => [ + ], + ], + ], + ], + 'opentracing' => [ + 'question' => 'Do you want to use hyperf/tracer component ? (An open tracing protocol component, adapte with Zipkin etc.)', + 'default' => 'n', + 'required' => false, + 'force' => true, + 'custom-package' => true, + 'options' => [ + 'y' => [ + 'name' => 'yes', + 'packages' => [ + 'hyperf/tracer', + ], + 'resources' => [ + 'resources/tracer/opentracing.php' => 'config/autoload/opentracing.php', + ], + ], + ], + ], + ], +]; diff --git a/installer/resources/amqp/amqp.php b/installer/resources/amqp/amqp.php new file mode 100755 index 0000000..934cf4b --- /dev/null +++ b/installer/resources/amqp/amqp.php @@ -0,0 +1,40 @@ + [ + 'host' => env('AMQP_HOST', 'localhost'), + 'port' => (int) env('AMQP_PORT', 5672), + 'user' => env('AMQP_USER', 'guest'), + 'password' => env('AMQP_PASSWORD', 'guest'), + 'vhost' => env('AMQP_VHOST', '/'), + 'concurrent' => [ + 'limit' => 1, + ], + 'pool' => [ + 'connections' => 2, + ], + 'params' => [ + 'insist' => false, + 'login_method' => 'AMQPLAIN', + 'login_response' => null, + 'locale' => 'en_US', + 'connection_timeout' => 3, + 'read_write_timeout' => 6, + 'context' => null, + 'keepalive' => true, + 'heartbeat' => 3, + 'channel_rpc_timeout' => 0.0, + 'close_on_destruct' => false, + 'max_idle_channels' => 10, + ], + ], +]; diff --git a/installer/resources/async_queue/AsyncQueueConsumer.php b/installer/resources/async_queue/AsyncQueueConsumer.php new file mode 100755 index 0000000..e211939 --- /dev/null +++ b/installer/resources/async_queue/AsyncQueueConsumer.php @@ -0,0 +1,20 @@ +logger = $container->get(LoggerFactory::class)->get('queue'); + } + + public function listen(): array + { + return [ + AfterHandle::class, + BeforeHandle::class, + FailedHandle::class, + RetryHandle::class, + ]; + } + + public function process(object $event): void + { + if ($event instanceof Event && $event->getMessage()->job()) { + $job = $event->getMessage()->job(); + $jobClass = get_class($job); + if ($job instanceof AnnotationJob) { + $jobClass = sprintf('Job[%s@%s]', $job->class, $job->method); + } + $date = date('Y-m-d H:i:s'); + + switch (true) { + case $event instanceof BeforeHandle: + $this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass)); + break; + case $event instanceof AfterHandle: + $this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass)); + break; + case $event instanceof FailedHandle: + $this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass)); + $this->logger->error((string) $event->getThrowable()); + break; + case $event instanceof RetryHandle: + $this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass)); + break; + } + } + } +} diff --git a/installer/resources/async_queue/async_queue.php b/installer/resources/async_queue/async_queue.php new file mode 100755 index 0000000..b115ddf --- /dev/null +++ b/installer/resources/async_queue/async_queue.php @@ -0,0 +1,27 @@ + [ + 'driver' => Hyperf\AsyncQueue\Driver\RedisDriver::class, + 'redis' => [ + 'pool' => 'default', + ], + 'channel' => '{queue}', + 'timeout' => 2, + 'retry_seconds' => 5, + 'handle_timeout' => 10, + 'processes' => 1, + 'concurrent' => [ + 'limit' => 10, + ], + ], +]; diff --git a/installer/resources/bin/hyperf.stub b/installer/resources/bin/hyperf.stub new file mode 100755 index 0000000..710ad5a --- /dev/null +++ b/installer/resources/bin/hyperf.stub @@ -0,0 +1,24 @@ +#!/usr/bin/env php +get(Hyperf\Contract\ApplicationInterface::class); + $application->run(); +})(); diff --git a/installer/resources/config_center/config_acm.php b/installer/resources/config_center/config_acm.php new file mode 100755 index 0000000..e08c65f --- /dev/null +++ b/installer/resources/config_center/config_acm.php @@ -0,0 +1,31 @@ + (bool) env('CONFIG_CENTER_ENABLE', true), + 'driver' => env('CONFIG_CENTER_DRIVER', 'aliyun_acm'), + 'mode' => env('CONFIG_CENTER_MODE', Mode::PROCESS), + 'drivers' => [ + 'aliyun_acm' => [ + 'driver' => Hyperf\ConfigAliyunAcm\AliyunAcmDriver::class, + 'interval' => 5, + 'endpoint' => env('ALIYUN_ACM_ENDPOINT', 'acm.aliyun.com'), + 'namespace' => env('ALIYUN_ACM_NAMESPACE', ''), + 'data_id' => env('ALIYUN_ACM_DATA_ID', ''), + 'group' => env('ALIYUN_ACM_GROUP', 'DEFAULT_GROUP'), + 'access_key' => env('ALIYUN_ACM_AK', ''), + 'secret_key' => env('ALIYUN_ACM_SK', ''), + 'ecs_ram_role' => env('ALIYUN_ACM_RAM_ROLE', ''), + ], + ], +]; diff --git a/installer/resources/config_center/config_apollo.php b/installer/resources/config_center/config_apollo.php new file mode 100755 index 0000000..289f176 --- /dev/null +++ b/installer/resources/config_center/config_apollo.php @@ -0,0 +1,36 @@ + (bool) env('CONFIG_CENTER_ENABLE', true), + 'driver' => env('CONFIG_CENTER_DRIVER', 'apollo'), + 'mode' => env('CONFIG_CENTER_MODE', Mode::PROCESS), + 'drivers' => [ + 'apollo' => [ + 'driver' => Hyperf\ConfigApollo\ApolloDriver::class, + 'pull_mode' => PullMode::INTERVAL, + 'server' => 'http://127.0.0.1:9080', + 'appid' => 'test', + 'cluster' => 'default', + 'namespaces' => [ + 'application', + ], + 'interval' => 5, + 'strict_mode' => false, + 'client_ip' => current(swoole_get_local_ip()), + 'pullTimeout' => 10, + 'interval_timeout' => 1, + ], + ], +]; diff --git a/installer/resources/config_center/config_etcd.php b/installer/resources/config_center/config_etcd.php new file mode 100755 index 0000000..b35139f --- /dev/null +++ b/installer/resources/config_center/config_etcd.php @@ -0,0 +1,32 @@ + (bool) env('CONFIG_CENTER_ENABLE', true), + 'driver' => env('CONFIG_CENTER_DRIVER', 'etcd'), + 'mode' => env('CONFIG_CENTER_MODE', Mode::PROCESS), + 'drivers' => [ + 'etcd' => [ + 'driver' => Hyperf\ConfigEtcd\EtcdDriver::class, + 'packer' => Hyperf\Utils\Packer\JsonPacker::class, + 'namespaces' => [ + '/application', + ], + 'mapping' => [ + // etcd key => config key + '/application/test' => 'test', + ], + 'interval' => 5, + ], + ], +]; diff --git a/installer/resources/config_center/config_nacos.php b/installer/resources/config_center/config_nacos.php new file mode 100755 index 0000000..ed4a105 --- /dev/null +++ b/installer/resources/config_center/config_nacos.php @@ -0,0 +1,39 @@ + (bool) env('CONFIG_CENTER_ENABLE', true), + 'driver' => env('CONFIG_CENTER_DRIVER', 'nacos'), + 'mode' => env('CONFIG_CENTER_MODE', Mode::PROCESS), + 'drivers' => [ + 'nacos' => [ + 'driver' => Hyperf\ConfigNacos\NacosDriver::class, + 'merge_mode' => Hyperf\ConfigNacos\Constants::CONFIG_MERGE_OVERWRITE, + 'interval' => 3, + 'default_key' => 'nacos_config', + 'listener_config' => [ + // dataId, group, tenant, type, content + 'nacos_config' => [ + 'tenant' => 'tenant', // corresponding with service.namespaceId + 'data_id' => 'hyperf-service-config', + 'group' => 'DEFAULT_GROUP', + ], + 'nacos_config.data' => [ + 'data_id' => 'hyperf-service-config-yml', + 'group' => 'DEFAULT_GROUP', + 'type' => 'yml', + ], + ], + ], + ], +]; diff --git a/installer/resources/config_center/etcd.php b/installer/resources/config_center/etcd.php new file mode 100755 index 0000000..5aa800f --- /dev/null +++ b/installer/resources/config_center/etcd.php @@ -0,0 +1,18 @@ + 'http://127.0.0.1:2379', + 'version' => 'v3beta', + 'options' => [ + 'timeout' => 10, + ], +]; diff --git a/installer/resources/config_center/nacos.php b/installer/resources/config_center/nacos.php new file mode 100755 index 0000000..0a2d02e --- /dev/null +++ b/installer/resources/config_center/nacos.php @@ -0,0 +1,24 @@ + '', + // The nacos host info + 'host' => '127.0.0.1', + 'port' => 8848, + // The nacos account info + 'username' => null, + 'password' => null, + 'guzzle' => [ + 'config' => null, + ], +]; diff --git a/installer/resources/constants/BusinessException.php b/installer/resources/constants/BusinessException.php new file mode 100755 index 0000000..eaae4aa --- /dev/null +++ b/installer/resources/constants/BusinessException.php @@ -0,0 +1,28 @@ + [ + 'driver' => env('DB_DRIVER', 'mysql'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'hyperf'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => env('DB_PREFIX', ''), + 'pool' => [ + 'min_connections' => 1, + 'max_connections' => 10, + 'connect_timeout' => 10.0, + 'wait_timeout' => 3.0, + 'heartbeat' => -1, + 'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60), + ], + 'commands' => [ + 'gen:model' => [ + 'path' => 'app/Model', + 'force_casts' => true, + 'inheritance' => 'Model', + 'uses' => '', + 'table_mapping' => [], + ], + ], + ], +]; diff --git a/installer/resources/database/redis.php b/installer/resources/database/redis.php new file mode 100755 index 0000000..3180b62 --- /dev/null +++ b/installer/resources/database/redis.php @@ -0,0 +1,27 @@ + [ + 'host' => env('REDIS_HOST', 'localhost'), + 'auth' => env('REDIS_AUTH', null), + 'port' => (int) env('REDIS_PORT', 6379), + 'db' => (int) env('REDIS_DB', 0), + 'pool' => [ + 'min_connections' => 1, + 'max_connections' => 10, + 'connect_timeout' => 10.0, + 'wait_timeout' => 3.0, + 'heartbeat' => -1, + 'max_idle_time' => (float) env('REDIS_MAX_IDLE_TIME', 60), + ], + ], +]; diff --git a/installer/resources/jsonrpc/services.php b/installer/resources/jsonrpc/services.php new file mode 100755 index 0000000..656b616 --- /dev/null +++ b/installer/resources/jsonrpc/services.php @@ -0,0 +1,29 @@ + [ + [ + // The service name, this name should as same as with the name of service provider. + 'name' => 'YourServiceName', + // The service registry, if `nodes` is missing below, then you should provide this configs. + 'registry' => [ + 'protocol' => 'consul', + 'address' => 'Enter the address of service registry', + ], + // If `registry` is missing, then you should provide the nodes configs. + 'nodes' => [ + // Provide the host and port of the service provider. + // ['host' => 'The host of the service provider', 'port' => 9502] + ], + ], + ], +]; diff --git a/installer/resources/model_cache/Model.php b/installer/resources/model_cache/Model.php new file mode 100755 index 0000000..fc074c3 --- /dev/null +++ b/installer/resources/model_cache/Model.php @@ -0,0 +1,21 @@ + [ + 'driver' => env('DB_DRIVER', 'mysql'), + 'host' => env('DB_HOST', 'localhost'), + 'port' => env('DB_PORT', 3306), + 'database' => env('DB_DATABASE', 'hyperf'), + 'username' => env('DB_USERNAME', 'root'), + 'password' => env('DB_PASSWORD', ''), + 'charset' => env('DB_CHARSET', 'utf8mb4'), + 'collation' => env('DB_COLLATION', 'utf8mb4_unicode_ci'), + 'prefix' => env('DB_PREFIX', ''), + 'pool' => [ + 'min_connections' => 1, + 'max_connections' => 10, + 'connect_timeout' => 10.0, + 'wait_timeout' => 3.0, + 'heartbeat' => -1, + 'max_idle_time' => (float) env('DB_MAX_IDLE_TIME', 60), + ], + 'cache' => [ + 'handler' => Hyperf\ModelCache\Handler\RedisHandler::class, + 'cache_key' => '{mc:%s:m:%s}:%s:%s', + 'prefix' => 'default', + 'ttl' => 3600 * 24, + 'empty_model_ttl' => 600, + 'load_script' => true, + ], + 'commands' => [ + 'gen:model' => [ + 'path' => 'app/Model', + 'force_casts' => true, + 'inheritance' => 'Model', + 'uses' => '', + 'table_mapping' => [], + ], + ], + ], +]; diff --git a/installer/resources/tracer/opentracing.php b/installer/resources/tracer/opentracing.php new file mode 100755 index 0000000..8b4dbd1 --- /dev/null +++ b/installer/resources/tracer/opentracing.php @@ -0,0 +1,49 @@ + env('TRACER_DRIVER', 'zipkin'), + 'enable' => [ + 'guzzle' => env('TRACER_ENABLE_GUZZLE', false), + 'redis' => env('TRACER_ENABLE_REDIS', false), + 'db' => env('TRACER_ENABLE_DB', false), + 'method' => env('TRACER_ENABLE_METHOD', false), + ], + 'tracer' => [ + 'zipkin' => [ + 'driver' => \Hyperf\Tracer\Adapter\ZipkinTracerFactory::class, + 'app' => [ + 'name' => env('APP_NAME', 'skeleton'), + // Hyperf will detect the system info automatically as the value if ipv4, ipv6, port is null + 'ipv4' => '127.0.0.1', + 'ipv6' => null, + 'port' => 9501, + ], + 'options' => [ + 'endpoint_url' => env('ZIPKIN_ENDPOINT_URL', 'http://localhost:9411/api/v2/spans'), + 'timeout' => env('ZIPKIN_TIMEOUT', 1), + ], + 'sampler' => BinarySampler::createAsAlwaysSample(), + ], + 'jaeger' => [ + 'driver' => \Hyperf\Tracer\Adapter\JaegerTracerFactory::class, + 'name' => env('APP_NAME', 'skeleton'), + 'options' => [ + 'local_agent' => [ + 'reporting_host' => env('JAEGER_REPORTING_HOST', 'localhost'), + 'reporting_port' => env('JAEGER_REPORTING_PORT', 5775), + ], + ], + ], + ], +];