mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-14 04:05:35 +08:00
Merge pull request #176 from zhamao-robot/docker-compose-support
增加 Docker Compose 支持
This commit is contained in:
@@ -22,14 +22,13 @@
|
||||
"onebot/libonebot": "dev-develop",
|
||||
"psr/container": "^2.0",
|
||||
"psy/psysh": "^0.11.8",
|
||||
"symfony/console": "^6.0 || ^5.0 || ^4.0",
|
||||
"symfony/console": "^6.0",
|
||||
"symfony/polyfill-ctype": "^1.19",
|
||||
"symfony/polyfill-mbstring": "^1.19",
|
||||
"symfony/polyfill-php80": "^1.16",
|
||||
"symfony/routing": "~6.0 || ~5.0 || ~4.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"brainmaestro/composer-git-hooks": "^2.8",
|
||||
"brainmaestro/composer-git-hooks": "^3.0",
|
||||
"friendsofphp/php-cs-fixer": "^3.2 != 3.7.0",
|
||||
"jetbrains/phpstorm-attributes": "^1.0",
|
||||
"phpstan/extension-installer": "^1.1",
|
||||
|
||||
96
docker-compose.yml
Normal file
96
docker-compose.yml
Normal file
@@ -0,0 +1,96 @@
|
||||
version: "3.9"
|
||||
|
||||
services:
|
||||
bot:
|
||||
build:
|
||||
context: docker/zhamao
|
||||
dockerfile: ./Dockerfile
|
||||
container_name: zhamao-bot
|
||||
depends_on:
|
||||
- database-postgres
|
||||
- database-redis
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "20001-20005:20001-20005/tcp"
|
||||
volumes:
|
||||
- ".:/bot"
|
||||
|
||||
walle-q:
|
||||
build:
|
||||
context: docker/walle-q
|
||||
dockerfile: ./Dockerfile
|
||||
container_name: zhamao-bot-walle-q
|
||||
depends_on:
|
||||
- bot
|
||||
networks:
|
||||
- bot-net
|
||||
volumes:
|
||||
- "./docker/volumes/walle-q-data:/bot"
|
||||
|
||||
database-postgres:
|
||||
container_name: zhamao-bot-db-postgres
|
||||
env_file:
|
||||
- docker/environment.env
|
||||
image: postgres:14.4-alpine
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "5432:5432/tcp"
|
||||
restart: always
|
||||
volumes:
|
||||
- "./docker/volumes/postgres-data:/var/lib/postgresql/data"
|
||||
- "./docker/volumes/postgres-init:/docker-entrypoint-initdb.d"
|
||||
|
||||
database-redis:
|
||||
command:
|
||||
- redis-server
|
||||
- --requirepass
|
||||
- "$${REDIS_PASSWORD}"
|
||||
container_name: zhamao-bot-db-redis
|
||||
env_file:
|
||||
- docker/environment.env
|
||||
image: redis:7.0-alpine
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "6379:6379/tcp"
|
||||
restart: always
|
||||
volumes:
|
||||
- "./docker/volumes/redis-data:/data"
|
||||
|
||||
db-admin-adminer:
|
||||
container_name: zhamao-bot-dbadmin-adminer
|
||||
depends_on:
|
||||
database-postgres:
|
||||
condition: service_started
|
||||
database-redis:
|
||||
condition: service_started
|
||||
env_file:
|
||||
- docker/environment.env
|
||||
image: adminer:latest
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "15432:8080/tcp"
|
||||
profiles:
|
||||
- with-adminer
|
||||
|
||||
db-admin-redis-insight:
|
||||
container_name: zhamao-bot-dbadmin-redis-insight
|
||||
depends_on:
|
||||
database-postgres:
|
||||
condition: service_started
|
||||
database-redis:
|
||||
condition: service_started
|
||||
image: redislabs/redisinsight:latest
|
||||
networks:
|
||||
- bot-net
|
||||
ports:
|
||||
- "16379:8001/tcp"
|
||||
profiles:
|
||||
- with-adminer
|
||||
|
||||
networks:
|
||||
bot-net:
|
||||
name: bot-net
|
||||
2
docker/.gitignore
vendored
Normal file
2
docker/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/volumes
|
||||
environment.env
|
||||
7
docker/environment.env.example
Normal file
7
docker/environment.env.example
Normal file
@@ -0,0 +1,7 @@
|
||||
REDIS_PASSWORD={{$REDIS_PASSWORD}}
|
||||
|
||||
POSTGRES_USER={{$DB_ROOT_USER}}
|
||||
POSTGRES_PASSWORD={{$DB_ROOT_PASSWORD}}
|
||||
POSTGRES_APPLICATION_USER={{$DB_USER}}
|
||||
POSTGRES_APPLICATION_USER_PASSWORD={{$DB_PASSWORD}}
|
||||
POSTGRES_APPLICATION_DATABASE={{$DB_NAME}}
|
||||
14
docker/walle-q/Dockerfile
Normal file
14
docker/walle-q/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM alpine:latest
|
||||
|
||||
MAINTAINER sunxyw <me@sunxyw.xyz>
|
||||
|
||||
RUN apk add --no-cache curl
|
||||
RUN mkdir -p /bot && \
|
||||
curl -fsSL https://github.com/onebot-walle/walle-q/releases/latest/download/walle-q-i686-linux-musl.tar.gz -O && \
|
||||
tar -zxvf walle-q-i686-linux-musl.tar.gz -C /bot && \
|
||||
rm -rf walle-q-i686-linux-musl.tar.gz && \
|
||||
chmod +x /bot/walle-q-i686-linux-musl
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
ENTRYPOINT [ "/bot/walle-q-i686-linux-musl" ]
|
||||
14
docker/zhamao/Dockerfile
Normal file
14
docker/zhamao/Dockerfile
Normal file
@@ -0,0 +1,14 @@
|
||||
FROM phpswoole/swoole:5.0-php8.1
|
||||
|
||||
MAINTAINER sunxyw <me@sunxyw.xyz>
|
||||
|
||||
WORKDIR "/bot"
|
||||
|
||||
ENV TZ=Asia/Shanghai
|
||||
|
||||
EXPOSE 20001-20005
|
||||
|
||||
RUN docker-php-ext-install pcntl
|
||||
|
||||
ENTRYPOINT [ "./zhamao" ]
|
||||
CMD [ "server" ]
|
||||
@@ -31,6 +31,9 @@ const ZM_ERR_METHOD_NOT_FOUND = 1; // 找不到方法
|
||||
const ZM_ERR_ROUTE_NOT_FOUND = 2; // 找不到路由
|
||||
const ZM_ERR_ROUTE_METHOD_NOT_ALLOWED = 3; // 路由方法不允许
|
||||
|
||||
const LOAD_MODE_VENDOR = 0; // 从 vendor 加载
|
||||
const LOAD_MODE_SRC = 1; // 从 src 加载
|
||||
|
||||
/* 定义工作目录 */
|
||||
define('WORKING_DIR', getcwd());
|
||||
|
||||
@@ -48,7 +51,7 @@ if (DIRECTORY_SEPARATOR === '\\') {
|
||||
}
|
||||
|
||||
/* 定义启动模式,这里指的是框架本身的源码目录是通过 composer 加入 vendor 加载的还是直接放到 src 目录加载的,前者为 1,后者为 0 */
|
||||
define('LOAD_MODE', is_dir(zm_dir(SOURCE_ROOT_DIR . '/src/ZM')) ? 0 : 1);
|
||||
define('LOAD_MODE', is_dir(zm_dir(SOURCE_ROOT_DIR . '/src/ZM')) ? LOAD_MODE_VENDOR : LOAD_MODE_SRC);
|
||||
|
||||
/* 定义框架本身所处的根目录,此处如果 LOAD_MODE 为 1 的话,框架自身的根目录在 vendor/zhamao/framework 子目录下 */
|
||||
if (Phar::running() !== '') {
|
||||
|
||||
136
src/ZM/Command/Command.php
Normal file
136
src/ZM/Command/Command.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Exception\ZMException;
|
||||
|
||||
abstract class Command extends \Symfony\Component\Console\Command\Command
|
||||
{
|
||||
/**
|
||||
* 输入
|
||||
*/
|
||||
protected InputInterface $input;
|
||||
|
||||
/**
|
||||
* 输出
|
||||
*
|
||||
* 一般来说同样会是 ConsoleOutputInterface
|
||||
*/
|
||||
protected OutputInterface $output;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @internal 不建议覆写此方法,建议使用 {@see handle()} 方法
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
{
|
||||
$this->input = $input;
|
||||
$this->output = $output;
|
||||
return $this->handle();
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令的主体
|
||||
*
|
||||
* @return int 命令执行结果 {@see self::SUCCESS} 或 {@see self::FAILURE} 或 {@see self::INVALID}
|
||||
*/
|
||||
abstract protected function handle(): int;
|
||||
|
||||
/**
|
||||
* 输出一段文本,默认样式
|
||||
*
|
||||
* @param string $message 要输出的文本
|
||||
* @param bool $newline 是否在文本后换行
|
||||
* @see OutputInterface::write()
|
||||
*/
|
||||
protected function write(string $message, bool $newline = true): void
|
||||
{
|
||||
$this->output->write($message, $newline);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出文本,一般用于提示信息
|
||||
*
|
||||
* @param string $message 要输出的文本
|
||||
* @param bool $newline 是否在文本后换行
|
||||
*/
|
||||
protected function info(string $message, bool $newline = true): void
|
||||
{
|
||||
$this->write("<info>{$message}</info>", $newline);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出文本,一般用于错误信息
|
||||
*
|
||||
* @param string $message 要输出的文本
|
||||
* @param bool $newline 是否在文本后换行
|
||||
*/
|
||||
protected function error(string $message, bool $newline = true): void
|
||||
{
|
||||
$this->write("<error>{$message}</error>", $newline);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出文本,一般用于警告或附注信息
|
||||
*
|
||||
* @param string $message 要输出的文本
|
||||
* @param bool $newline 是否在文本后换行
|
||||
*/
|
||||
protected function comment(string $message, bool $newline = true): void
|
||||
{
|
||||
$this->write("<comment>{$message}</comment>", $newline);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出文本,一般用于提问信息
|
||||
*
|
||||
* @param string $message 要输出的文本
|
||||
* @param bool $newline 是否在文本后换行
|
||||
*/
|
||||
protected function question(string $message, bool $newline = true): void
|
||||
{
|
||||
$this->write("<question>{$message}</question>", $newline);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出文本,一般用于详细信息
|
||||
*
|
||||
* @param string $message 要输出的文本
|
||||
* @param bool $newline 是否在文本后换行
|
||||
*/
|
||||
protected function detail(string $message, bool $newline = true): void
|
||||
{
|
||||
$this->write("<fg=gray>{$message}</>", $newline);
|
||||
}
|
||||
|
||||
/**
|
||||
* 输出一个区块,区块内内容可以覆写
|
||||
*
|
||||
* 此功能需要 $output 为 {@see ConsoleOutputInterface} 类型
|
||||
*
|
||||
* @param string $message 作为标题的文本
|
||||
* @param callable $callback 回调函数,接收一个参数,类型为 {@see ConsoleSectionOutput}
|
||||
*/
|
||||
protected function section(string $message, callable $callback): void
|
||||
{
|
||||
$output = $this->output;
|
||||
if (!$output instanceof ConsoleOutputInterface) {
|
||||
throw new \LogicException('Section 功能只能在 ConsoleOutputInterface 中使用');
|
||||
}
|
||||
|
||||
$this->info($message);
|
||||
$section = $output->section();
|
||||
try {
|
||||
$callback($section);
|
||||
} catch (ZMException $e) {
|
||||
$this->error($e->getMessage());
|
||||
exit(self::FAILURE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,114 +4,237 @@ declare(strict_types=1);
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\ConsoleSectionOutput;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Exception\InitException;
|
||||
|
||||
class InitCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'init';
|
||||
|
||||
private $extract_files = [
|
||||
'/zhamao',
|
||||
'/config/global.php',
|
||||
'/.gitignore',
|
||||
'/config/file_header.json',
|
||||
'/config/console_color.json',
|
||||
'/config/motd.txt',
|
||||
'/src/Module/Example/Hello.php',
|
||||
'/src/Module/Middleware/TimerMiddleware.php',
|
||||
'/src/Custom/global_function.php',
|
||||
];
|
||||
private string $base_path;
|
||||
|
||||
protected function configure()
|
||||
private bool $force = false;
|
||||
|
||||
protected function configure(): void
|
||||
{
|
||||
$this->setDescription('Initialize framework starter | 初始化框架运行的基础文件');
|
||||
$this->setDescription('初始化框架运行的基础文件');
|
||||
$this->setDefinition([
|
||||
new InputOption('force', 'F', null, '强制重制,覆盖现有文件'),
|
||||
new InputOption('force', 'f', InputOption::VALUE_NONE, '覆盖现有文件'),
|
||||
new InputOption('docker', null, InputOption::VALUE_NONE, '启用 Docker 支持'),
|
||||
]);
|
||||
$this->setHelp("此命令将会解压以下文件到项目的根目录:\n" . implode("\n", $this->getExtractFiles()));
|
||||
// ...
|
||||
$this->setHelp('提取框架的基础文件到当前目录,以便于快速开始开发。');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output): int
|
||||
protected function handle(): int
|
||||
{
|
||||
if (LOAD_MODE === 1) { // 从composer依赖而来的项目模式,最基本的需要初始化的模式
|
||||
$output->writeln('<comment>Initializing files</comment>');
|
||||
$base_path = WORKING_DIR;
|
||||
$args = $input->getOption('force');
|
||||
foreach ($this->extract_files as $file) {
|
||||
if (!file_exists($base_path . $file) || $args) {
|
||||
$info = pathinfo($file);
|
||||
@mkdir($base_path . $info['dirname'], 0777, true);
|
||||
echo 'Copying ' . $file . PHP_EOL;
|
||||
$package_name = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true)['name'];
|
||||
copy($base_path . '/vendor/' . $package_name . $file, $base_path . $file);
|
||||
} else {
|
||||
echo 'Skipping ' . $file . ' , file exists.' . PHP_EOL;
|
||||
$this->setBasePath();
|
||||
$this->force = $this->input->getOption('force');
|
||||
|
||||
$this->section('提取框架基础文件', function (ConsoleSectionOutput $section) {
|
||||
$this->extractFiles($this->getExtractFiles(), $section);
|
||||
});
|
||||
|
||||
if (LOAD_MODE === LOAD_MODE_SRC) {
|
||||
$this->section('应用自动加载配置', function (ConsoleSectionOutput $section) {
|
||||
$autoload = [
|
||||
'psr-4' => [
|
||||
'Module\\' => 'src/Module',
|
||||
'Custom\\' => 'src/Custom',
|
||||
],
|
||||
'files' => [
|
||||
'src/Custom/global_function.php',
|
||||
],
|
||||
];
|
||||
|
||||
$section->write('<fg=gray>更新 composer.json ... </>');
|
||||
|
||||
if (!file_exists($this->base_path . '/composer.json')) {
|
||||
throw new InitException('未找到 composer.json 文件', '请检查当前目录是否为项目根目录', 41);
|
||||
}
|
||||
}
|
||||
chmod($base_path . '/zhamao', 0755);
|
||||
$autoload = [
|
||||
'psr-4' => [
|
||||
'Module\\' => 'src/Module',
|
||||
'Custom\\' => 'src/Custom',
|
||||
],
|
||||
'files' => [
|
||||
'src/Custom/global_function.php',
|
||||
],
|
||||
];
|
||||
if (file_exists($base_path . '/composer.json')) {
|
||||
$composer = json_decode(file_get_contents($base_path . '/composer.json'), true);
|
||||
|
||||
try {
|
||||
$composer = json_decode(file_get_contents($this->base_path . '/composer.json'), true, 512, JSON_THROW_ON_ERROR);
|
||||
} catch (\JsonException $e) {
|
||||
throw new InitException('解析 composer.json 文件失败', '请检查 composer.json 文件是否存在语法错误', 42, $e);
|
||||
}
|
||||
|
||||
if (!isset($composer['autoload'])) {
|
||||
$composer['autoload'] = $autoload;
|
||||
} else {
|
||||
foreach ($autoload['psr-4'] as $k => $v) {
|
||||
if (!isset($composer['autoload']['psr-4'][$k])) {
|
||||
$composer['autoload']['psr-4'][$k] = $v;
|
||||
}
|
||||
}
|
||||
foreach ($autoload['files'] as $v) {
|
||||
if (!in_array($v, $composer['autoload']['files'])) {
|
||||
$composer['autoload']['files'][] = $v;
|
||||
}
|
||||
}
|
||||
$composer['autoload'] = array_merge_recursive($composer['autoload'], $autoload);
|
||||
}
|
||||
file_put_contents($base_path . '/composer.json', json_encode($composer, 64 | 128 | 256));
|
||||
$output->writeln('<info>Executing composer command: `composer dump-autoload`</info>');
|
||||
|
||||
try {
|
||||
file_put_contents($this->base_path . '/composer.json', json_encode($composer, JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
} catch (\JsonException $e) {
|
||||
throw new InitException('写入 composer.json 文件失败', '', 0, $e);
|
||||
}
|
||||
|
||||
$section->writeln('<info>完成</info>');
|
||||
|
||||
$section->write('<fg=gray>执行 composer dump-autoload ... </>');
|
||||
exec('composer dump-autoload');
|
||||
echo PHP_EOL;
|
||||
} else {
|
||||
echo zm_internal_errcode('E00041') . "Error occurred. Please check your updates.\n";
|
||||
return 1;
|
||||
}
|
||||
$output->writeln('<info>Done!</info>');
|
||||
return 0;
|
||||
|
||||
$section->writeln('<info>完成</info>');
|
||||
});
|
||||
}
|
||||
if (LOAD_MODE === 2) { // 从phar启动的框架包,初始化的模式
|
||||
$phar_link = new \Phar(__DIR__);
|
||||
$current_dir = pathinfo($phar_link->getPath())['dirname'];
|
||||
chdir($current_dir);
|
||||
$phar_link = 'phar://' . $phar_link->getPath();
|
||||
foreach ($this->extract_files as $file) {
|
||||
if (!file_exists($current_dir . $file)) {
|
||||
$info = pathinfo($file);
|
||||
@mkdir($current_dir . $info['dirname'], 0777, true);
|
||||
echo 'Copying ' . $file . PHP_EOL;
|
||||
file_put_contents($current_dir . $file, file_get_contents($phar_link . $file));
|
||||
|
||||
if ($this->input->getOption('docker')) {
|
||||
$this->section('应用 Docker 支持', function (ConsoleSectionOutput $section) {
|
||||
$files = $this->getFilesFromPatterns([
|
||||
'/docker/*/Dockerfile',
|
||||
'/docker/environment.env.example',
|
||||
'/docker-compose.yml',
|
||||
]);
|
||||
$this->extractFiles($files, $section);
|
||||
|
||||
// 生成 env 文件
|
||||
if ($this->shouldExtractFile('/docker/environment.env')) {
|
||||
$section->write('<fg=gray>生成环境变量文件 ... </>');
|
||||
$env = file_get_contents($this->base_path . '/docker/environment.env.example');
|
||||
foreach ($this->getEnvVariables() as $key => $value) {
|
||||
$env = $this->injectEnv($env, $key, $value);
|
||||
}
|
||||
file_put_contents($this->base_path . '/docker/environment.env', $env);
|
||||
$section->writeln('<info>完成</info>');
|
||||
} else {
|
||||
echo 'Skipping ' . $file . ' , file exists.' . PHP_EOL;
|
||||
$section->writeln('<fg=gray>生成环境变量文件 ... </><comment>跳过(已存在)</comment>');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
$output->writeln(zm_internal_errcode('E00042') . 'initialization must be started with composer-project mode!');
|
||||
return 1;
|
||||
|
||||
// 将命令行入口标记为可执行
|
||||
chmod($this->base_path . '/zhamao', 0755);
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
private function getExtractFiles(): array
|
||||
{
|
||||
return $this->extract_files;
|
||||
$patterns = [
|
||||
'/zhamao',
|
||||
'/.gitignore',
|
||||
'/config/*',
|
||||
'/src/Globals/*.php',
|
||||
];
|
||||
|
||||
return $this->getFilesFromPatterns($patterns);
|
||||
}
|
||||
|
||||
private function getFilesFromPatterns(array $patterns): array
|
||||
{
|
||||
$files = [];
|
||||
foreach ($patterns as $pattern) {
|
||||
// TODO: 优化代码,避免在循环中使用 array_merge 以减少资源消耗
|
||||
$files = array_merge($files, glob($this->getVendorPath($pattern), GLOB_BRACE));
|
||||
}
|
||||
return array_map(function ($file) {
|
||||
return str_replace($this->getVendorPath(''), '', $file);
|
||||
}, $files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置基准目录
|
||||
*/
|
||||
private function setBasePath(): void
|
||||
{
|
||||
$base_path = WORKING_DIR;
|
||||
if (file_exists($base_path . '/vendor/autoload.php')) {
|
||||
$this->base_path = $base_path;
|
||||
} else {
|
||||
$phar_link = new \Phar(__DIR__);
|
||||
$current_dir = pathinfo($phar_link->getPath())['dirname'];
|
||||
chdir($current_dir);
|
||||
$phar_link = 'phar://' . $phar_link->getPath();
|
||||
if (file_exists($phar_link . '/vendor/autoload.php')) {
|
||||
$this->base_path = $current_dir;
|
||||
} else {
|
||||
throw new InitException('框架启动模式不是 Composer 模式,无法进行初始化', '如果您是从 Github 下载的框架,请参阅文档进行源码模式启动', 42);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取文件
|
||||
*
|
||||
* @param string $file 文件路径,相对于框架根目录
|
||||
* @throws InitException 提取失败时抛出异常
|
||||
*/
|
||||
private function extractFile(string $file): void
|
||||
{
|
||||
$info = pathinfo($file);
|
||||
// 确保目录存在
|
||||
if (
|
||||
!file_exists($this->base_path . $info['dirname'])
|
||||
&& !mkdir($concurrent_dir = $this->base_path . $info['dirname'], 0777, true)
|
||||
&& !is_dir($concurrent_dir)
|
||||
) {
|
||||
throw new InitException("无法创建目录 {$concurrent_dir}", '请检查目录权限');
|
||||
}
|
||||
|
||||
if (copy($this->getVendorPath($file), $this->base_path . $file) === false) {
|
||||
throw new InitException("无法复制文件 {$file}", '请检查目录权限');
|
||||
}
|
||||
}
|
||||
|
||||
private function shouldExtractFile(string $file): bool
|
||||
{
|
||||
return !file_exists($this->base_path . $file) || $this->force;
|
||||
}
|
||||
|
||||
private function getVendorPath(string $file): string
|
||||
{
|
||||
try {
|
||||
$package_name = json_decode(file_get_contents(__DIR__ . '/../../../composer.json'), true, 512, JSON_THROW_ON_ERROR)['name'];
|
||||
} catch (\JsonException) {
|
||||
throw new InitException('无法读取框架包的 composer.json', '请检查框架包完整性,或者重新安装框架包');
|
||||
}
|
||||
return $this->base_path . '/vendor/' . $package_name . $file;
|
||||
}
|
||||
|
||||
private function extractFiles(array $files, OutputInterface $output): void
|
||||
{
|
||||
foreach ($files as $file) {
|
||||
$output->write("<fg=gray>提取 {$file} ... </>");
|
||||
if ($this->shouldExtractFile($file)) {
|
||||
try {
|
||||
$this->extractFile($file);
|
||||
$output->write('<info>完成</info>');
|
||||
} catch (InitException $e) {
|
||||
$output->write('<error>失败</error>');
|
||||
throw $e;
|
||||
} finally {
|
||||
$output->writeln('');
|
||||
}
|
||||
} else {
|
||||
$output->writeln('<comment>跳过(已存在)</comment>');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function injectEnv(string $env, string $key, string $value): string
|
||||
{
|
||||
$pattern = "/^{$key}=.+$/m";
|
||||
if (preg_match($pattern, $env)) {
|
||||
return preg_replace($pattern, "{$key}={$value}", $env);
|
||||
}
|
||||
|
||||
return $env . PHP_EOL . "{$key}={$value}";
|
||||
}
|
||||
|
||||
private function getEnvVariables(): array
|
||||
{
|
||||
return [
|
||||
'REDIS_PASSWORD' => bin2hex(random_bytes(8)),
|
||||
|
||||
'POSTGRES_USER' => 'root',
|
||||
'POSTGRES_PASSWORD' => bin2hex(random_bytes(8)),
|
||||
|
||||
'POSTGRES_APPLICATION_DATABASE' => 'zhamao',
|
||||
'POSTGRES_APPLICATION_USER' => 'zhamao',
|
||||
'POSTGRES_APPLICATION_USER_PASSWORD' => bin2hex(random_bytes(8)),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use ZM\Command\Server\ServerReloadCommand;
|
||||
use ZM\Command\Server\ServerStartCommand;
|
||||
use ZM\Command\Server\ServerStatusCommand;
|
||||
use ZM\Command\Server\ServerStopCommand;
|
||||
use ZM\Exception\InitException;
|
||||
use ZM\Exception\SingletonViolationException;
|
||||
|
||||
/**
|
||||
* 命令行启动的入口文件,用于初始化环境变量,并启动命令行应用
|
||||
@@ -30,13 +30,10 @@ final class ConsoleApplication extends Application
|
||||
{
|
||||
private static $obj;
|
||||
|
||||
/**
|
||||
* @throws InitException
|
||||
*/
|
||||
public function __construct(string $name = 'zhamao-framework')
|
||||
{
|
||||
if (self::$obj !== null) {
|
||||
throw new InitException(zm_internal_errcode('E00069') . 'Initializing another Application is not allowed!');
|
||||
throw new SingletonViolationException(self::class);
|
||||
}
|
||||
|
||||
// 初始化命令
|
||||
|
||||
@@ -6,9 +6,4 @@ namespace ZM\Exception;
|
||||
|
||||
class InitException extends ZMException
|
||||
{
|
||||
public function __construct(string $message = '', int $code = 0, ?\Throwable $previous = null)
|
||||
{
|
||||
// TODO: change this to a better error message
|
||||
parent::__construct($message, '', $code, $previous);
|
||||
}
|
||||
}
|
||||
|
||||
17
src/ZM/Exception/SingletonViolationException.php
Normal file
17
src/ZM/Exception/SingletonViolationException.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZM\Exception;
|
||||
|
||||
class SingletonViolationException extends ZMException
|
||||
{
|
||||
public function __construct(string $singleton_class_name)
|
||||
{
|
||||
parent::__construct(
|
||||
"类 {$singleton_class_name} 是单例模式,不允许初始化多个实例。",
|
||||
"请检查代码,确保只初始化了一个 {$singleton_class_name} 实例。",
|
||||
69
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ use ZM\Event\Listener\ManagerEventListener;
|
||||
use ZM\Event\Listener\MasterEventListener;
|
||||
use ZM\Event\Listener\WorkerEventListener;
|
||||
use ZM\Event\Listener\WSEventListener;
|
||||
use ZM\Exception\InitException;
|
||||
use ZM\Exception\SingletonViolationException;
|
||||
use ZM\Exception\ZMKnownException;
|
||||
use ZM\Logger\TablePrinter;
|
||||
use ZM\Process\ProcessStateManager;
|
||||
@@ -69,14 +69,13 @@ class Framework
|
||||
* 框架初始化文件
|
||||
*
|
||||
* @param array<string, null|bool|string> $argv 传入的参数(见 ServerStartCommand)
|
||||
* @throws InitException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(array $argv = [])
|
||||
{
|
||||
// 单例化整个Framework类
|
||||
if (self::$instance !== null) {
|
||||
throw new InitException(zm_internal_errcode('E00069') . 'Initializing another Framework in one instance is not allowed!');
|
||||
throw new SingletonViolationException(self::class);
|
||||
}
|
||||
self::$instance = $this;
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ declare(strict_types=1);
|
||||
namespace ZM;
|
||||
|
||||
use ZM\Command\Server\ServerStartCommand;
|
||||
use ZM\Exception\InitException;
|
||||
use ZM\Exception\SingletonViolationException;
|
||||
use ZM\Plugin\InstantPlugin;
|
||||
|
||||
class InstantApplication extends InstantPlugin
|
||||
@@ -16,14 +16,10 @@ class InstantApplication extends InstantPlugin
|
||||
/** @var array 存储要传入的args */
|
||||
private array $args = [];
|
||||
|
||||
/**
|
||||
* @param null|mixed $dir
|
||||
* @throws InitException
|
||||
*/
|
||||
public function __construct($dir = null)
|
||||
public function __construct(mixed $dir = null)
|
||||
{
|
||||
if (self::$obj !== null) {
|
||||
throw new InitException(zm_internal_errcode('E00069') . 'Initializing another Application is not allowed!');
|
||||
throw new SingletonViolationException(self::class);
|
||||
}
|
||||
self::$obj = $this; // 用于标记已经初始化完成
|
||||
parent::__construct($dir ?? (__DIR__ . '/../..'));
|
||||
|
||||
Reference in New Issue
Block a user