├── .gitignore
├── function
├── functions.php
└── shortcut.php
├── engine
├── base
│ ├── Openly.php
│ ├── QuietException.php
│ ├── ResponseException.php
│ ├── TraitModel.php
│ ├── SwooleLock.php
│ ├── BaseException.php
│ └── Lock.php
├── rpc
│ ├── RpcMatrix.php
│ └── RpcHost.php
├── model
│ ├── validator
│ │ ├── TypeFilter.php
│ │ ├── TypeAssignment.php
│ │ ├── TypeEnding.php
│ │ ├── assignment
│ │ │ └── DefaultValidator.php
│ │ ├── checker
│ │ │ ├── EmailValidator.php
│ │ │ ├── UrlValidator.php
│ │ │ ├── IpValidator.php
│ │ │ ├── BitsetValidator.php
│ │ │ ├── RegularValidator.php
│ │ │ ├── SetValidator.php
│ │ │ ├── StringValidator.php
│ │ │ └── NumberValidator.php
│ │ ├── TypeChecker.php
│ │ ├── filter
│ │ │ └── FilterValidator.php
│ │ ├── ending
│ │ │ ├── RequiredValidator.php
│ │ │ └── UniqueValidator.php
│ │ └── ValidatorProperty.php
│ └── ModelException.php
├── db
│ ├── entity
│ │ ├── SchemaAbstract.php
│ │ ├── SchemaException.php
│ │ ├── DbField.php
│ │ └── FieldType.php
│ ├── query
│ │ └── builder
│ │ │ ├── schema
│ │ │ ├── PartitionSchema.php
│ │ │ ├── LimitSchema.php
│ │ │ ├── WithSchema.php
│ │ │ ├── DeleteSchema.php
│ │ │ ├── UnionSchema.php
│ │ │ ├── HavingSchema.php
│ │ │ ├── SelectModifierSchema.php
│ │ │ ├── GroupSchema.php
│ │ │ ├── UpdateSchema.php
│ │ │ ├── InsertSelectSchema.php
│ │ │ ├── WindowSchema.php
│ │ │ ├── FrameSchema.php
│ │ │ ├── OrderSchema.php
│ │ │ ├── TableSchema.php
│ │ │ ├── SelectSchema.php
│ │ │ └── JoinSchema.php
│ │ │ ├── statement
│ │ │ ├── InsertSelectStatement.php
│ │ │ └── InsertStatement.php
│ │ │ └── RawBuilder.php
│ ├── proxy
│ │ ├── TransactionException.php
│ │ └── SimpleTransaction.php
│ ├── Shortcut.php
│ ├── connector
│ │ ├── DbEachIterator.php
│ │ ├── DbPoolProductionConfig.php
│ │ └── DbPool.php
│ └── active
│ │ ├── DceExtendColumn.php
│ │ ├── DceModifyRecord.php
│ │ ├── ActiveException.php
│ │ ├── DceExternalRecord.php
│ │ └── CacheEngine.php
├── loader
│ ├── Decorator.php
│ └── attr
│ │ ├── Constructor.php
│ │ ├── Singleton.php
│ │ ├── Sington.php
│ │ └── SingMatrix.php
├── i18n
│ ├── TextMapping.php
│ └── Locale.php
├── cache
│ ├── CacheException.php
│ ├── CacheClearable.php
│ └── engine
│ │ └── RedisCache.php
├── project
│ ├── ProjectException.php
│ ├── render
│ │ ├── RawRenderer.php
│ │ ├── RenderException.php
│ │ ├── JsonRenderer.php
│ │ ├── JsonpRenderer.php
│ │ ├── template
│ │ │ ├── status.php
│ │ │ └── exception.php
│ │ └── XmlRenderer.php
│ ├── node
│ │ ├── NodeException.php
│ │ └── NodeArgument.php
│ ├── request
│ │ ├── RouterException.php
│ │ ├── RequestException.php
│ │ ├── RawRequest.php
│ │ ├── RequestManager.php
│ │ └── Pageable.php
│ ├── session
│ │ ├── SessionException.php
│ │ ├── CookieCgi.php
│ │ └── Cookie.php
│ ├── Project.php
│ └── ProjectManager.php
├── sharding
│ ├── middleware
│ │ ├── Middleware.php
│ │ ├── data_processor
│ │ │ ├── DataProcessor.php
│ │ │ ├── DbWriteProcessor.php
│ │ │ └── DbReadEachIterator.php
│ │ ├── DirectiveParser.php
│ │ └── MiddlewareException.php
│ ├── id_generator
│ │ ├── client
│ │ │ ├── IdgClientIncrement.php
│ │ │ ├── IdgClientRpc.php
│ │ │ └── IdgClientTime.php
│ │ ├── bridge
│ │ │ ├── IdgRequestRpc.php
│ │ │ ├── IdgRequestInterface.php
│ │ │ ├── IdgStorageRedis.php
│ │ │ ├── IdgRequestLocal.php
│ │ │ ├── IdgStorage.php
│ │ │ ├── IdgStorageFile.php
│ │ │ └── IdgBatch.php
│ │ ├── server
│ │ │ ├── IdgServerRpc.php
│ │ │ └── IdgServerProducer.php
│ │ └── IdgException.php
│ └── parser
│ │ ├── StatementParser.php
│ │ ├── mysql
│ │ ├── MysqlCompareExpressionParser.php
│ │ ├── MysqlStatementParser.php
│ │ ├── list
│ │ │ ├── MysqlGroupByParser.php
│ │ │ ├── MysqlOrderByParser.php
│ │ │ └── MysqlColumnParser.php
│ │ ├── statement
│ │ │ ├── MysqlColumnItemParser.php
│ │ │ ├── MysqlWhenParser.php
│ │ │ └── MysqlOrderByConditionParser.php
│ │ ├── MysqlListParser.php
│ │ └── MysqlValueParser.php
│ │ └── StatementParserException.php
├── config
│ ├── ConfigLibInterface.php
│ └── ConfigException.php
├── storage
│ └── redis
│ │ ├── RedisProxySimple.php
│ │ ├── RedisPoolProductionConfig.php
│ │ ├── RedisPool.php
│ │ ├── RedisConnector.php
│ │ └── RedisProxyPool.php
├── log
│ ├── SimpleLogger.php
│ ├── FileOutput.php
│ ├── MatrixOutput.php
│ ├── MatrixLogger.php
│ └── HttpOutput.php
├── pool
│ ├── ArrayChannel.php
│ ├── CoroutineChannel.php
│ ├── TcpPoolProductionConfig.php
│ ├── PoolException.php
│ ├── ChannelAbstract.php
│ ├── TcpPool.php
│ └── PoolProduct.php
└── run.php
├── drunk
├── units.php
├── debug
│ ├── output
│ │ ├── CliDebug.php
│ │ └── LogDebug.php
│ └── DebugException.php
└── ArrayTree.php
├── project
├── dce
│ ├── service
│ │ ├── server
│ │ │ ├── RpcServerApi.php
│ │ │ ├── ConnectionException.php
│ │ │ ├── Connection.php
│ │ │ └── ServerApi.php
│ │ ├── cron
│ │ │ ├── CrontabBasic.php
│ │ │ ├── CrontabCoroutine.php
│ │ │ └── TaskIterator.php
│ │ └── extender
│ │ │ ├── Extender.php
│ │ │ └── ExtenderException.php
│ └── controller
│ │ ├── ShardingController.php
│ │ ├── RpcController.php
│ │ ├── UtilityController.php
│ │ ├── CrontabController.php
│ │ └── DceController.php
├── http
│ ├── service
│ │ ├── HttpException.php
│ │ └── CookieSwoole.php
│ ├── config
│ │ ├── config.php
│ │ └── nodes.php
│ └── controller
│ │ └── HttpServerController.php
├── tcp
│ ├── service
│ │ ├── TcpException.php
│ │ ├── RawRequestUdp.php
│ │ └── RawRequestTcp.php
│ ├── config
│ │ ├── config.php
│ │ └── nodes.php
│ └── controller
│ │ └── TcpServerController.php
└── websocket
│ ├── service
│ └── WebsocketException.php
│ ├── config
│ ├── config.php
│ └── nodes.php
│ └── controller
│ └── WebsocketServerController.php
├── LICENSE
├── composer.json
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | .ignore
3 | .ignore-git
4 | vendor
5 | composer.lock
6 |
--------------------------------------------------------------------------------
/function/functions.php:
--------------------------------------------------------------------------------
1 | shardingQuery();
14 | }
15 |
16 | abstract protected function shardingQuery(): void;
17 | }
--------------------------------------------------------------------------------
/engine/sharding/middleware/data_processor/DataProcessor.php:
--------------------------------------------------------------------------------
1 | sourceData[] = $resultData;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/client/IdgClientIncrement.php:
--------------------------------------------------------------------------------
1 | check($this->getValue());
12 | if ($result instanceof ValidatorException) {
13 | throw $result;
14 | }
15 | return true;
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/project/websocket/service/WebsocketException.php:
--------------------------------------------------------------------------------
1 | [
9 | 'host' => '0.0.0.0',
10 | 'port' => 20460,
11 | 'service' => '\\http\\service\\HttpServer',
12 | ],
13 | 'swoole_http' => [
14 | 'enable_static_handler' => true,
15 | 'document_root' => APP_WWW,
16 | ],
17 | '#extends' => [
18 | APP_COMMON . 'config/http.php',
19 | ],
20 | ];
21 |
--------------------------------------------------------------------------------
/engine/base/ResponseException.php:
--------------------------------------------------------------------------------
1 | [
9 | 'host' => '0.0.0.0',
10 | 'port' => 20461,
11 | 'service' => '\\websocket\\service\\WebsocketServer',
12 | ],
13 | 'swoole_websocket' => [
14 | // 'enable_static_handler' => true,
15 | // 'document_root' => APP_WWW,
16 | ],
17 | '#extends' => [ // 扩展用户自定义的配置
18 | APP_COMMON . 'config/websocket.php',
19 | ]
20 | ];
21 |
--------------------------------------------------------------------------------
/drunk/debug/output/CliDebug.php:
--------------------------------------------------------------------------------
1 | format($dataFormatted);
16 | echo $content;
17 | $this->logStorage && $this->logStorage->push($this->getPath(), $content);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/server/IdgServerRpc.php:
--------------------------------------------------------------------------------
1 | idGenerator->newServer()->register($tag);
11 | }
12 |
13 | public static function generate(string $tag): IdgBatch {
14 | return Dce::$config->idGenerator->newServer()->generate($tag);
15 | }
16 | }
--------------------------------------------------------------------------------
/project/dce/controller/RpcController.php:
--------------------------------------------------------------------------------
1 | implode("\n", $output), 'code' => $code];
14 | }
15 |
16 | protected static function sleep(): void {
17 | sleep(self::getIntervalSeconds());
18 | }
19 | }
--------------------------------------------------------------------------------
/engine/model/validator/assignment/DefaultValidator.php:
--------------------------------------------------------------------------------
1 | getProperty('default')->value ?? null);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/bridge/IdgRequestInterface.php:
--------------------------------------------------------------------------------
1 | request->cli['--type'] ?? $this->request->cli['-t'] ?? 'file';
17 | Dce::$cache->{$cacheType}->clear();
18 | $this->print('缓存清除完毕');
19 | }
20 | }
--------------------------------------------------------------------------------
/engine/project/node/NodeException.php:
--------------------------------------------------------------------------------
1 | [
9 | 'host' => '0.0.0.0',
10 | 'port' => 20462,
11 | 'mode' => SWOOLE_PROCESS,
12 | 'sock_type' => SWOOLE_SOCK_TCP,
13 | 'service' => '\\tcp\\service\\TcpServer',
14 | 'extra_ports' => [
15 | ['host' => '0.0.0.0', 'port' => 20463, 'sock_type' => SWOOLE_SOCK_UDP], // 同时监听20463端口的Udp服务
16 | ],
17 | ],
18 | '#extends' => [ // 扩展用户自定义的配置
19 | APP_COMMON . 'config/tcp.php',
20 | ],
21 | ];
22 |
--------------------------------------------------------------------------------
/engine/project/request/RouterException.php:
--------------------------------------------------------------------------------
1 | array;
18 | }
19 |
20 | public function arrayify(bool $withWrap = true): array {
21 | $array = $this->array;
22 | $array['children'] = $this->childrenArrayify();
23 | return $withWrap ? $array : $array['children'];
24 | }
25 | }
--------------------------------------------------------------------------------
/engine/sharding/middleware/data_processor/DbWriteProcessor.php:
--------------------------------------------------------------------------------
1 | sourceData[0]) && throw new MiddlewareException(MiddlewareException::INSERT_FAILED_NO_ID);
14 | return $this->sourceData[0];
15 | }
16 |
17 | public function queryGetAffectedCount(): int {
18 | return array_sum($this->sourceData);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/project/dce/service/cron/CrontabCoroutine.php:
--------------------------------------------------------------------------------
1 | parent::run($task, $now));
14 | }
15 |
16 | /** @inheritDoc */
17 | protected static function exec(string $command): array {
18 | return System::exec($command);
19 | }
20 |
21 | protected static function sleep(): void {
22 | System::sleep(self::getIntervalSeconds());
23 | }
24 | }
--------------------------------------------------------------------------------
/engine/storage/redis/RedisProxySimple.php:
--------------------------------------------------------------------------------
1 | redis = $pool[$key];
17 | } else {
18 | $pool[$key] = $this->redis = (new RedisConnector(Dce::$config->redis))->getRedis();
19 | }
20 | parent::__construct($index, $noSerialize);
21 | }
22 | }
--------------------------------------------------------------------------------
/engine/db/proxy/TransactionException.php:
--------------------------------------------------------------------------------
1 | config();
15 | $topic = self::applyTime($topic, time());
16 | $config['console'] && LogManager::console($content ? "$topic\n" . self::slim($content) : "$topic", prefix: '');
17 | ($logFile = LogManager::standardConfigLogfile($config)) && LogManager::write($logFile, $content ? "$topic\n$content" : $topic);
18 | }
19 | }
--------------------------------------------------------------------------------
/engine/pool/ArrayChannel.php:
--------------------------------------------------------------------------------
1 | channel, $object);
15 | }
16 |
17 | /** @inheritDoc */
18 | public function pop(): mixed {
19 | return array_pop($this->channel);
20 | }
21 |
22 | public function isEmpty(): bool {
23 | return empty($this->channel);
24 | }
25 |
26 | public function length(): int {
27 | return count($this->channel);
28 | }
29 | }
--------------------------------------------------------------------------------
/engine/model/validator/checker/EmailValidator.php:
--------------------------------------------------------------------------------
1 | addError($this->getGeneralError(null, lang(ValidatorException::INVALID_EMAIL)));
20 | }
21 |
22 | return $this->getError();
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/engine/model/validator/TypeChecker.php:
--------------------------------------------------------------------------------
1 | getValue();
12 | if (! empty($value)) {
13 | $result = $this->check($value);
14 | if ($result instanceof ValidatorException) {
15 | throw $result;
16 | }
17 | }
18 | return true;
19 | }
20 |
21 | /**
22 | * @param mixed $value
23 | * @return ValidatorException|null
24 | */
25 | abstract protected function check(mixed $value):? ValidatorException;
26 | }
27 |
--------------------------------------------------------------------------------
/engine/model/validator/checker/UrlValidator.php:
--------------------------------------------------------------------------------
1 | addError($this->getGeneralError(null, lang(ValidatorException::INVALID_URL)));
20 | }
21 |
22 | return $this->getError();
23 | }
24 | }
--------------------------------------------------------------------------------
/engine/model/validator/filter/FilterValidator.php:
--------------------------------------------------------------------------------
1 | getProperty('regexp')) {
19 | $value = preg_replace($regexp->value, '', $value);
20 | } else if ($keyword = $this->getProperty('keyword')) {
21 | $value = str_replace($keyword->value, '', $value);
22 | }
23 | return $value;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/engine/log/FileOutput.php:
--------------------------------------------------------------------------------
1 | genPath($path);
17 | ! is_dir($fileDir = dirname($filePath)) && mkdir($fileDir, 0766, true);
18 | $content .= "\n";
19 | $logMethod === LogMethod::Prepend && $content .= file_get_contents($filePath) ?: '';
20 | file_put_contents($filePath, $content, ($logMethod === LogMethod::Append ? FILE_APPEND : 0) | LOCK_EX);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/drunk/debug/output/LogDebug.php:
--------------------------------------------------------------------------------
1 | data = $data;
19 | return $this;
20 | }
21 |
22 | protected function output(array $dataFormatted): void {
23 | ! $this->logStorage && throw new DebugException(DebugException::SET_STORAGE_FIRST);
24 | $content = $this->format($dataFormatted);
25 | $this->logStorage->push($this->getPath(), $content); // 储存调试内容
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/LimitSchema.php:
--------------------------------------------------------------------------------
1 | 0) {
14 | $this->pushCondition($limit, false);
15 | if ($offset > 0) {
16 | $this->pushCondition($offset, true);
17 | }
18 | } else {
19 | // 否则清空Limit条件
20 | $this->pushCondition(false);
21 | }
22 | }
23 |
24 | public function __toString(): string {
25 | return implode(',', $this->getConditions());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/function/shortcut.php:
--------------------------------------------------------------------------------
1 | getProperty('isIp4')->value ? FILTER_FLAG_IPV4 : FILTER_FLAG_IPV6)) {
21 | $this->addError($this->getGeneralError(null, lang(ValidatorException::INVALID_IP)));
22 | }
23 |
24 | return $this->getError();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/engine/run.php:
--------------------------------------------------------------------------------
1 | statement = trim($statement);
19 | $this->statementLength = mb_strlen($statement);
20 | $this->offset = & $offset;
21 | }
22 |
23 | protected static function isMinus(string $word): bool {
24 | return '-' === $word;
25 | }
26 |
27 | abstract public function toArray(): array;
28 |
29 | abstract public function __toString(): string;
30 | }
31 |
--------------------------------------------------------------------------------
/drunk/debug/DebugException.php:
--------------------------------------------------------------------------------
1 | channel = new Channel($capacity);
17 | }
18 |
19 | public function push($object): bool {
20 | return $this->channel->push($object);
21 | }
22 |
23 | public function pop(): mixed {
24 | return $this->channel->pop();
25 | }
26 |
27 | public function isEmpty(): bool {
28 | return $this->channel->isEmpty();
29 | }
30 |
31 | public function length(): int {
32 | return $this->channel->length();
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/engine/project/render/JsonRenderer.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'application/json; charset=utf-8');
18 | }
19 |
20 | /** @inheritDoc */
21 | protected function rendering(Controller $controller, mixed $data): string {
22 | return json_encode(false === $data ? ($controller->getAllAssignedStatus() ?: new stdClass) : $data, JSON_UNESCAPED_UNICODE);
23 | }
24 | }
--------------------------------------------------------------------------------
/engine/config/ConfigException.php:
--------------------------------------------------------------------------------
1 | getProperty('set', lang(ValidatorException::SET_REQUIRED));
18 | if ($set && ($bits = array_reduce($set->value, fn($tb, $b) => $tb | $b, 0)) && (($value | $bits) !== $bits)) {
19 | $this->addError($this->getGeneralError($set->error, lang(ValidatorException::VALUE_NOT_IN_BITSET)));
20 | }
21 |
22 | return $this->getError();
23 | }
24 | }
--------------------------------------------------------------------------------
/engine/project/session/CookieCgi.php:
--------------------------------------------------------------------------------
1 | 'cli',
10 | 'path' => 'tcp',
11 | ],
12 | [
13 | 'path' => 'start',
14 | 'name' => '启动服务',
15 | 'controller' => 'TcpServerController->start',
16 | ],
17 | [
18 | 'path' => 'stop',
19 | 'name' => '停止服务',
20 | 'controller' => 'TcpServerController->stop',
21 | 'enable_coroutine' => true,
22 | ],
23 | [
24 | 'path' => 'reload',
25 | 'name' => '重启服务',
26 | 'controller' => 'TcpServerController->reload',
27 | 'enable_coroutine' => true,
28 | ],
29 | [
30 | 'path' => 'status',
31 | 'name' => '状态信息',
32 | 'controller' => 'TcpServerController->status',
33 | 'enable_coroutine' => true,
34 | ],
35 | ];
36 |
--------------------------------------------------------------------------------
/project/http/config/nodes.php:
--------------------------------------------------------------------------------
1 | 'cli',
10 | 'path' => 'http',
11 | ],
12 | [
13 | 'path' => 'start',
14 | 'name' => '启动服务',
15 | 'controller' => 'HttpServerController->start',
16 | ],
17 | [
18 | 'path' => 'stop',
19 | 'name' => '停止服务',
20 | 'controller' => 'HttpServerController->stop',
21 | 'enable_coroutine' => true,
22 | ],
23 | [
24 | 'path' => 'reload',
25 | 'name' => '重启服务',
26 | 'controller' => 'HttpServerController->reload',
27 | 'enable_coroutine' => true,
28 | ],
29 | [
30 | 'path' => 'status',
31 | 'name' => '状态信息',
32 | 'controller' => 'HttpServerController->status',
33 | 'enable_coroutine' => true,
34 | ],
35 | ];
36 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/client/IdgClientRpc.php:
--------------------------------------------------------------------------------
1 | idGenerator->newClient($tag)->generate($tag, $uid, $geneTag);
16 | }
17 |
18 | public static function batchGenerate(string $tag, int $count, int|string $uid = 0, string|null $geneTag = null): array {
19 | return Dce::$config->idGenerator->newClient($tag)->batchGenerate($tag, $count, $uid, $geneTag);
20 | }
21 |
22 | public static function getClient(string $tag): IdGenerator {
23 | return Dce::$config->idGenerator->newClient($tag);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/engine/project/node/NodeArgument.php:
--------------------------------------------------------------------------------
1 | setProperties($properties);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/project/dce/controller/CrontabController.php:
--------------------------------------------------------------------------------
1 | run($this->rawRequest->remainingPaths[0] ?? '', time());
22 | }
23 |
24 | #[Node]
25 | public function status(): void {
26 | $this->print(Crontab::inst()->showLog(true));
27 | }
28 |
29 | #[Node]
30 | public function history(): void {
31 | $this->print(Crontab::inst()->showLog(false));
32 | }
33 | }
--------------------------------------------------------------------------------
/project/websocket/config/nodes.php:
--------------------------------------------------------------------------------
1 | 'cli',
10 | 'path' => 'websocket',
11 | ],
12 | [
13 | 'path' => 'start',
14 | 'name' => '启动服务',
15 | 'controller' => 'WebsocketServerController->start',
16 | ],
17 | [
18 | 'path' => 'stop',
19 | 'name' => '停止服务',
20 | 'controller' => 'WebsocketServerController->stop',
21 | 'enable_coroutine' => true,
22 | ],
23 | [
24 | 'path' => 'reload',
25 | 'name' => '重启服务',
26 | 'controller' => 'WebsocketServerController->reload',
27 | 'enable_coroutine' => true,
28 | ],
29 | [
30 | 'path' => 'status',
31 | 'name' => '状态信息',
32 | 'controller' => 'WebsocketServerController->status',
33 | 'enable_coroutine' => true,
34 | ],
35 | ];
36 |
--------------------------------------------------------------------------------
/engine/model/validator/ending/RequiredValidator.php:
--------------------------------------------------------------------------------
1 | getProperty('allowEmpty');
18 | if (! $allowEmpty->value) {
19 | $this->addError($this->getGeneralError(null, lang(ValidatorException::CANNOT_BE_EMPTY)));
20 | } else if (false === $value) {
21 | $this->addError($this->getGeneralError(null, lang(ValidatorException::REQUIRED_MISSING)));
22 | }
23 | }
24 | return $this->getError();
25 | }
26 | }
--------------------------------------------------------------------------------
/engine/log/MatrixOutput.php:
--------------------------------------------------------------------------------
1 | root = preg_replace('/\/+?$/', '/', $this->root);
20 | }
21 |
22 | /**
23 | * 拼储存路径
24 | * @param string $path
25 | * @return string
26 | */
27 | protected function genPath(string $path): string {
28 | return $this->root . ltrim($path, '/');
29 | }
30 |
31 | /**
32 | * 压入调试内容
33 | * @param string $path
34 | * @param string $content
35 | * @param LogMethod $logMethod
36 | */
37 | abstract public function push(string $path, string $content, LogMethod $logMethod = LogMethod::Append): void;
38 | }
39 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/WithSchema.php:
--------------------------------------------------------------------------------
1 | self::tableWrapThrow($c), $columns);
11 | $recursive ??= count($select->getUnionSchema()->getConditions()) === 1;
12 | $this->pushCondition([$recursive ? 'RECURSIVE ' : '', $name, $columns ? '(' . implode(', ', $columns) . ')' : '', $select]);
13 | $this->mergeParams($select->getParams());
14 | }
15 |
16 | public function __toString(): string {
17 | return implode(', ', array_map(fn($c) => "$c[0]$c[1]$c[2] AS ($c[3])", $this->getConditions()));
18 | }
19 | }
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "idrunk/dce",
3 | "description": "DCE, A network programming framework with sharding middleware",
4 | "keywords": ["dce", "swoole", "websocket", "sharding", "rcr", "rpc", "pool"],
5 | "homepage": "https://drunkce.com",
6 | "type": "library",
7 | "license": "MulanPSL-2.0",
8 | "authors": [
9 | {
10 | "name": "Drunk",
11 | "homepage": "http://idrunk.net"
12 | }
13 | ],
14 | "require": {
15 | "php": ">=8.1",
16 | "swoole/ide-helper": "@dev"
17 | },
18 | "require-dev": {
19 | "fakerphp/faker": "^1.13",
20 | "vimeo/psalm": "^4.9"
21 | },
22 | "suggest": {
23 | "ext-pdo": "*",
24 | "ext-curl": "*",
25 | "ext-posix": "*",
26 | "ext-sockets": "*",
27 | "ext-redis": "*",
28 | "ext-memcache": "*",
29 | "ext-memcached": "*"
30 | },
31 | "autoload": {
32 | "files": ["engine/run.php"]
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/DeleteSchema.php:
--------------------------------------------------------------------------------
1 | pushCondition($table);
23 | }
24 | }
25 | }
26 |
27 | public function __toString(): string {
28 | return implode(',', $this->getConditions());
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/engine/log/MatrixLogger.php:
--------------------------------------------------------------------------------
1 | $targetLength ? '...' : '');
19 | }
20 |
21 | /**
22 | * @param LoggerType $type
23 | * @param string $topic
24 | * @param string|null $content
25 | */
26 | abstract public function push(LoggerType $type, string $topic, string|null $content = null): void;
27 |
28 | public static function inst(): static {
29 | return Singleton::gen(static::class);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/engine/project/render/JsonpRenderer.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'application/javascript; charset=utf-8');
18 | }
19 |
20 | /** @inheritDoc */
21 | protected function rendering(Controller $controller, mixed $data): string {
22 | $callback = $controller->request->get[$controller->request->node->jsonpCallback];
23 | $response = json_encode(false === $data ? ($controller->getAllAssignedStatus() ?: new stdClass) : $data, JSON_UNESCAPED_UNICODE);
24 | return "{$callback}({$response})";
25 | }
26 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/UnionSchema.php:
--------------------------------------------------------------------------------
1 | pushCondition([$statement, $isAll]);
17 | $this->mergeParams($statement->getParams());
18 | $this->logHasSubQuery(true);
19 | }
20 |
21 | public function __toString(): string {
22 | $conditions = [];
23 | foreach ($this->getConditions() as [$statement, $isAll]) {
24 | $conditions[] = ($isAll ? 'UNION ALL ' : 'UNION ' ) . sprintf($statement instanceof RawBuilder ? '%s' : '(%s)', $statement);
25 | }
26 | return implode(' ', $conditions);
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/engine/log/HttpOutput.php:
--------------------------------------------------------------------------------
1 | genPath($path);
14 | $content .= "\n";
15 | $ch = curl_init();
16 | curl_setopt($ch, CURLOPT_URL, $url);
17 | if ('https' === strtolower(parse_url($url, PHP_URL_SCHEME))) {
18 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
19 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
20 | }
21 | curl_setopt($ch, CURLOPT_POST, 1);
22 | curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: text/plain', 'Dce-Debug: 1', "Log-Type: $logMethod->value"]);
23 | curl_setopt($ch, CURLOPT_POSTFIELDS, $content);
24 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
25 | curl_exec($ch);
26 | }
27 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/HavingSchema.php:
--------------------------------------------------------------------------------
1 | whereSchema = new WhereSchema();
18 | }
19 |
20 | public function addCondition (string|array|RawBuilder|WhereSchema $column, string|float|RawBuilder|SelectStatement $operator, string|float|RawBuilder|SelectStatement $value) {
21 | $this->whereSchema->addCondition($column, $operator, $value, 'AND');
22 | $this->mergeConditions($this->whereSchema->getConditions());
23 | $this->mergeParams($this->whereSchema->getParams());
24 | }
25 |
26 | public function __toString(): string {
27 | return (string) $this->whereSchema;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/bridge/IdgStorageRedis.php:
--------------------------------------------------------------------------------
1 | prefix = $prefix;
18 | $this->index = $index;
19 | }
20 |
21 | /** @inheritDoc */
22 | protected function genKey(string $tag, string $prefix = ''): string {
23 | return "$prefix$this->prefix:$tag";
24 | }
25 |
26 | /** @inheritDoc */
27 | public function load(string $tag): IdgBatch|null {
28 | return RedisProxy::new($this->index)->get($this->genKey($tag)) ?: null;
29 | }
30 |
31 | /** @inheritDoc */
32 | public function save(string $tag, IdgBatch $batch): void {
33 | RedisProxy::new($this->index)->set($this->genKey($tag), $batch);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/engine/pool/TcpPoolProductionConfig.php:
--------------------------------------------------------------------------------
1 | matchWithProperties($config, ['host', 'port']);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/project/dce/service/extender/Extender.php:
--------------------------------------------------------------------------------
1 | cli->input($value);
35 | }
36 |
37 | protected function print(string $value, string $suffix = "\n"): void {
38 | $value = date('[H:i:s] ') . $value;
39 | $this->cli->print($value, $suffix);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/bridge/IdgRequestLocal.php:
--------------------------------------------------------------------------------
1 | server = $server;
17 | }
18 |
19 | /**
20 | * 注意, 实际环境中您需要自己实现request接口, 集群环境中服务端应该部署在远程服务器上, 您应该基于RPC等方式实现接口来实现注册及生成ID池
21 | * @param string $tag
22 | * @return IdgBatch
23 | * @throws IdgException
24 | */
25 | public function register(string $tag): IdgBatch {
26 | return $this->server->register($tag);
27 | }
28 |
29 | /**
30 | * @param string $tag
31 | * @return IdgBatch
32 | * @throws IdgException
33 | */
34 | public function generate(string $tag): IdgBatch {
35 | return $this->server->generate($tag);
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/engine/model/validator/checker/RegularValidator.php:
--------------------------------------------------------------------------------
1 | getProperty('regexp', lang(ValidatorException::REGEXP_REQUIRED));
22 | if ($regexp) {
23 | if (! Char::isRegexp($regexp->value)) {
24 | $this->addError(lang(ValidatorException::INVALID_REGEXP));
25 | } else if (! preg_match($regexp->value, $value)) {
26 | $this->addError($this->getGeneralError($regexp->error, lang(ValidatorException::INVALID_INPUT)));
27 | }
28 | }
29 |
30 | return $this->getError();
31 | }
32 | }
--------------------------------------------------------------------------------
/engine/storage/redis/RedisPoolProductionConfig.php:
--------------------------------------------------------------------------------
1 | matchWithProperties($config, ['host', 'port']);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/engine/project/session/Cookie.php:
--------------------------------------------------------------------------------
1 | format($modifier);
22 | }
23 | $this->pushCondition($upperModifier);
24 | }
25 | $this->modifier = $upperModifier;
26 | }
27 |
28 | public function __toString(): string {
29 | return $this->modifier;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/IdgException.php:
--------------------------------------------------------------------------------
1 | $propertyName = $value;
25 | return $this;
26 | }
27 |
28 | public function setProperties(array $properties): static {
29 | foreach ($properties as $name => $property)
30 | $this->setProperty($name, $property);
31 | return $this;
32 | }
33 |
34 | public function arrayify(): array {
35 | $properties = get_object_vars($this);
36 | foreach ($properties as $k => $property)
37 | if (! is_scalar($property)) unset($properties[$k]);
38 | return $properties;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/engine/model/validator/checker/SetValidator.php:
--------------------------------------------------------------------------------
1 | getProperty('set', lang(ValidatorException::SET_REQUIRED));
29 | $type = $this->getProperty('type');
30 | if ($set && ! (($type->value ?? 0) & self::TYPE_KEY_EXISTS ? key_exists($value, $set->value) : in_array($value, $set->value))) {
31 | $this->addError($this->getGeneralError($set->error, lang(ValidatorException::VALUE_NOT_IN_SET)));
32 | }
33 |
34 | return $this->getError();
35 | }
36 | }
--------------------------------------------------------------------------------
/engine/project/request/RequestException.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 | =$status ? '✔️' : '❌'?> =$message ?? ''?>
19 |
38 |
39 |
40 |
41 | -
42 | =$status ? '✔️' : '❌'?>
43 | =$code ?? ''?>
44 |
45 | - =$message ?? ''?>
46 |
47 |
48 |
--------------------------------------------------------------------------------
/engine/db/Shortcut.php:
--------------------------------------------------------------------------------
1 | table($tableName, $alias);
19 | }
20 | return $query;
21 | }
22 | }
23 |
24 | if (! function_exists('raw')) {
25 | /**
26 | * 实例化一个原始SQL语句对象
27 | * @param string $sql
28 | * @param bool|array $autoParenthesis
29 | * @param array $params
30 | * @return \dce\db\query\builder\RawBuilder
31 | * @throws \dce\db\query\QueryException
32 | */
33 | function raw(string $sql, bool|array $autoParenthesis = true, array $params = []): dce\db\query\builder\RawBuilder {
34 | return new dce\db\query\builder\RawBuilder($sql, $autoParenthesis, $params);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/engine/base/SwooleLock.php:
--------------------------------------------------------------------------------
1 | ', '<', '>=', '<='];
20 |
21 | public function toArray(): array {
22 | return [
23 | 'type' => 'compare_expression',
24 | 'name' => $this->operator,
25 | 'left' => $this->left->toArray(),
26 | 'right' => $this->right->toArray(),
27 | ];
28 | }
29 |
30 | public function __toString(): string {
31 | return "$this->left$this->operator$this->right";
32 | }
33 |
34 | public static function build(MysqlParser $left, string $operator, MysqlParser $right): self {
35 | $instance = new self('');
36 | $instance->left = $left;
37 | $instance->operator = $operator;
38 | $instance->right = $right;
39 | return $instance;
40 | }
41 | }
--------------------------------------------------------------------------------
/engine/base/BaseException.php:
--------------------------------------------------------------------------------
1 |
13 |
14 |
15 |
16 |
17 |
18 | =($code ?? 0) >= 500 ? '💥' : '💢'?> =$message ?? ''?>
19 |
38 |
39 |
40 |
41 | -
42 | =($code ?? 0) >= 500 ? '💥' : '💢'?>
43 | =$code ?? ''?>
44 |
45 | - =$message ?? ''?>
46 |
47 |
48 |
--------------------------------------------------------------------------------
/engine/storage/redis/RedisPool.php:
--------------------------------------------------------------------------------
1 | getRedis();
26 | }
27 |
28 | public function fetch(): Redis {
29 | return $this->get();
30 | }
31 |
32 | public static function inst(string ... $identities): static {
33 | return parent::getInstance(RedisPoolProductionConfig::class, ... $identities);
34 | }
35 |
36 | protected function retryable(Throwable $throwable): bool {
37 | return $throwable instanceof RedisException && preg_match(self::REGEXP_CONNECTION_LOST, $throwable->getMessage());
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/project/http/service/CookieSwoole.php:
--------------------------------------------------------------------------------
1 | rawRequest = $requestHttpSwoole;
16 | }
17 |
18 | /** @inheritDoc */
19 | public function get(string $key): mixed {
20 | return $this->rawRequest->getRaw()->cookie[$key] ?? null;
21 | }
22 |
23 | /** @inheritDoc */
24 | public function set(string $key, string $value = "", int $expire = 0, string $path = "", string $domain = "", bool $secure = false, bool $httpOnly = false): void {
25 | $this->rawRequest->getResponse()->cookie($key, $value, $expire, $path, $domain, $secure, $httpOnly);
26 | }
27 |
28 | /** @inheritDoc */
29 | public function delete(string $key): void {
30 | $this->rawRequest->getResponse()->cookie($key, null);
31 | }
32 |
33 | /** @inheritDoc */
34 | public function getAll(string $key): array {
35 | return $this->rawRequest->getRaw()->cookie ?? [];
36 | }
37 | }
--------------------------------------------------------------------------------
/engine/pool/ChannelAbstract.php:
--------------------------------------------------------------------------------
1 | 0 && $this->setCapacity($capacity);
17 | }
18 |
19 | /**
20 | * 可能某些池有容量上限, 可通过计数与此值比对是否超上限, 若无上限, 则无需调用此参数 (实例配置实例带有实例上限数, 该处已做过一次超限拦截)
21 | * @param int $capacity
22 | */
23 | public function setCapacity(int $capacity): void {
24 | $this->capacity = $capacity;
25 | }
26 |
27 | /**
28 | * @param mixed $object
29 | * @return bool
30 | */
31 | abstract public function push(mixed $object): bool;
32 |
33 | /**
34 | * @return mixed
35 | */
36 | abstract public function pop(): mixed;
37 |
38 | abstract public function isEmpty(): bool;
39 |
40 | abstract public function length(): int;
41 |
42 | public static function autoNew(int $capacity = 0): static {
43 | return Sington::new(SwooleUtility::inSwoole() ? CoroutineChannel::class : ArrayChannel::class, $capacity);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/engine/db/connector/DbEachIterator.php:
--------------------------------------------------------------------------------
1 | statement = $statement;
25 | $this->count = $statement->rowCount();
26 | $this->decorator = $decorator;
27 | }
28 |
29 | public function valid() {
30 | return $this->offset < $this->count;
31 | }
32 |
33 | public function rewind() {
34 | $this->offset = 0;
35 | }
36 |
37 | public function next() {
38 | $this->offset ++;
39 | }
40 |
41 | public function key() {
42 | return $this->offset;
43 | }
44 |
45 | public function current() {
46 | $data = $this->statement->fetch(PDO::FETCH_ASSOC);
47 | if ($this->decorator) {
48 | $data = call_user_func($this->decorator, $data);
49 | }
50 | return $data;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/engine/sharding/middleware/data_processor/DbReadEachIterator.php:
--------------------------------------------------------------------------------
1 | result = $result;
21 | $this->decorator = $decorator;
22 | }
23 |
24 | public function valid() {
25 | return $this->validBool;
26 | }
27 |
28 | public function rewind() {
29 | reset($this->result);
30 | $this->validBool = true;
31 | }
32 |
33 | public function next() {
34 | if (false === next($this->result)) {
35 | $this->validBool = false;
36 | }
37 | }
38 |
39 | public function key() {
40 | return key($this->result);
41 | }
42 |
43 | public function current() {
44 | $data = current($this->result);
45 | if ($this->decorator) {
46 | $data = call_user_func($this->decorator, $data);
47 | }
48 | return $data;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/bridge/IdgStorage.php:
--------------------------------------------------------------------------------
1 | procLock($this->genKey($tag, 'didg:'));
19 | }
20 |
21 | /**
22 | * 解进程锁
23 | * @param string $tag
24 | */
25 | public function unlock(string $tag): void {
26 | Dce::$lock->procUnlock($this->genKey($tag, 'didg:'));
27 | }
28 |
29 | /**
30 | * 生成缓存键/文件名
31 | * @param string $tag
32 | * @param string $prefix
33 | * @return string
34 | */
35 | abstract protected function genKey(string $tag, string $prefix = ''): string;
36 |
37 | /**
38 | * 加载配置数据
39 | * @param string $tag
40 | * @return IdgBatch|null
41 | */
42 | abstract public function load(string $tag): IdgBatch|null;
43 |
44 | /**
45 | * 储存配置数据
46 | * @param string $tag
47 | * @param IdgBatch $batch
48 | */
49 | abstract public function save(string $tag, IdgBatch $batch): void;
50 | }
51 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/bridge/IdgStorageFile.php:
--------------------------------------------------------------------------------
1 | dataDir = $dataDir;
14 | if (! file_exists($this->dataDir)) {
15 | mkdir($this->dataDir, 0777, true);
16 | }
17 | }
18 |
19 | /** @inheritDoc */
20 | protected function genKey(string $tag, string $prefix = ''): string {
21 | return "{$prefix}{$this->dataDir}{$tag}.php";
22 | }
23 |
24 | /** @inheritDoc */
25 | public function load(string $tag): IdgBatch|null {
26 | $content = @file_get_contents($this->genKey($tag));
27 | if ($content) {
28 | $batch = unserialize($content);
29 | if ($batch instanceof IdgBatch) {
30 | return $batch;
31 | }
32 | }
33 | return null;
34 | }
35 |
36 | /** @inheritDoc */
37 | public function save(string $tag, IdgBatch $batch): void {
38 | $content = serialize($batch);
39 | file_put_contents($this->genKey($tag), $content);
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/engine/db/active/DceExtendColumn.php:
--------------------------------------------------------------------------------
1 |
16 | * create table dce_extend_column (
17 | * table_id tinyint unsigned not null,
18 | * primary_id bigint unsigned not null,
19 | * column_id tinyint unsigned not null,
20 | * value varbinary(255) not null,
21 | * primary key (table_id, primary_id, column_id)
22 | * );
23 | *
24 | */
25 | class DceExtendColumn extends DceExternalRecord {
26 | #[Property(id: ExternalPropertyId::COLUMN_ID), DbField(FieldType::Smallint, primary: true)]
27 | public int $columnId;
28 | #[Property(id: ExternalPropertyId::VALUE), DbField(FieldType::Varchar, 255)]
29 | public string $value;
30 |
31 | public static function getColumnProperty(): Property {
32 | return static::getPropertyById(ExternalPropertyId::ColumnId->value);
33 | }
34 | public static function getValueProperty(): Property {
35 | return static::getPropertyById(ExternalPropertyId::Value->value);
36 | }
37 | }
--------------------------------------------------------------------------------
/engine/db/connector/DbPoolProductionConfig.php:
--------------------------------------------------------------------------------
1 | matchWithProperties($config, ['host', 'dbName']);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/engine/db/active/DceModifyRecord.php:
--------------------------------------------------------------------------------
1 |
16 | * create table dce_modify_record (
17 | * table_id tinyint unsigned not null,
18 | * primary_id bigint unsigned not null,
19 | * version smallint unsigned not null,
20 | * original_record json not null,
21 | * primary key (table_id, primary_id, version)
22 | * );
23 | *
24 | */
25 | class DceModifyRecord extends DceExternalRecord {
26 | #[Property(id: ExternalPropertyId::VERSION), DbField(FieldType::Smallint, primary: true)]
27 | public int $version;
28 | #[Property(id: ExternalPropertyId::ORIGINAL_RECORD), DbField(FieldType::Json)]
29 | public string $originalRecord;
30 |
31 | public static function getVersionProperty(): Property {
32 | return static::getPropertyById(ExternalPropertyId::Version->value);
33 | }
34 | public static function getOriginalRecordProperty(): Property {
35 | return static::getPropertyById(ExternalPropertyId::OriginalRecord->value);
36 | }
37 | }
--------------------------------------------------------------------------------
/engine/storage/redis/RedisConnector.php:
--------------------------------------------------------------------------------
1 | redis = new Redis();
20 | if ($config['password'] ?? false) {
21 | $this->redis->auth($config['password']);
22 | }
23 | if (
24 | $persistent
25 | ? ! $this->redis->pconnect($config['host'], $config['port'], self::CONNECT_TIMEOUT)
26 | : ! $this->redis->connect($config['host'], $config['port'], self::CONNECT_TIMEOUT)
27 | ) {
28 | throw new PoolException(PoolException::CONNECT_REDIS_FAILED);
29 | }
30 | if ($config['index'] > 0) {
31 | $this->redis->select($config['index']);
32 | }
33 | $this->redis->setOption(Redis::OPT_SCAN, Redis::SCAN_RETRY);
34 | $this->redis->setOption(Redis::OPT_SERIALIZER, Redis::SERIALIZER_PHP);
35 | }
36 |
37 | public function getRedis(): Redis {
38 | return $this->redis;
39 | }
40 | }
--------------------------------------------------------------------------------
/engine/sharding/parser/StatementParserException.php:
--------------------------------------------------------------------------------
1 | __NAMESPACE__ . '\statement\MysqlCaseParser',
19 | self::$whenStmt => __NAMESPACE__ . '\statement\MysqlWhenParser',
20 | default => false,
21 | };
22 | }
23 |
24 | public static function build(string $statement, int & $offset, string|null $statementName = null): static|null {
25 | if (is_subclass_of(static::class, self::class)) {
26 | $class = static::class;
27 | } else {
28 | $class = self::detect($statementName);
29 | }
30 | if ($class) {
31 | $instance = new $class($statement, $offset);
32 | if ($instance instanceof self) {
33 | $instance->parse();
34 | return $instance;
35 | }
36 | }
37 | return null;
38 | }
39 |
40 | abstract protected function parse(): void;
41 | }
42 |
--------------------------------------------------------------------------------
/project/tcp/controller/TcpServerController.php:
--------------------------------------------------------------------------------
1 | request->config->tcp['service'];
20 | if (! is_a($serverClass, TcpServer::class, true)) {
21 | throw new ConfigException(ConfigException::TCP_SERVICE_INVALID);
22 | }
23 | // 构造函数内会挂载RPC客户端, 所以整个公共的呗
24 | $this->server = new $serverClass();
25 | }
26 |
27 | public function start() {
28 | $this->server->start($this->request->pureCli);
29 | }
30 |
31 | public function stop() {
32 | RpcServerApi::stop();
33 | $this->print('Tcp server was stopped.');
34 | }
35 |
36 | public function reload() {
37 | RpcServerApi::reload();
38 | $this->print('Tcp server was reloaded.');
39 | }
40 |
41 | public function status() {
42 | $status = RpcServerApi::status();
43 | $status = json_encode($status, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
44 | $this->print($status);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/engine/model/validator/checker/StringValidator.php:
--------------------------------------------------------------------------------
1 | addError($this->getGeneralError(null, lang(ValidatorException::INVALID_STRING)));
24 | } else {
25 | $min = $this->getProperty('min');
26 | $max = $this->getProperty('max');
27 | if ($min || $max) {
28 | $length = mb_strlen($value);
29 |
30 | if ($min && $length < $min->value) {
31 | $this->addError($this->getGeneralError($min->error, lang(ValidatorException::CANNOT_LESS_THAN)));
32 | }
33 |
34 | if ($max && $length > $max->value) {
35 | $this->addError($this->getGeneralError($max->error, lang(ValidatorException::CANNOT_MORE_THAN)));
36 | }
37 | }
38 | }
39 |
40 | return $this->getError();
41 | }
42 | }
--------------------------------------------------------------------------------
/engine/db/entity/DbField.php:
--------------------------------------------------------------------------------
1 | setDefault($default);
37 | '' !== $comment && $this->setComment($comment);
38 | $type && $this->setType($type, $length, $unsigned, $precision);
39 | $primary && $this->setPrimary();
40 | $null && $this->setNull();
41 | $increment && $this->setIncrement();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/project/http/controller/HttpServerController.php:
--------------------------------------------------------------------------------
1 | request->config->http['service'] ?? '';
20 | if (! is_a($serverClass, HttpServer::class, true)) {
21 | throw new ConfigException(ConfigException::HTTP_SERVICE_INVALID);
22 | }
23 | // 在这里初始化是因为需要准备RpcClient
24 | $this->server = new $serverClass();
25 | }
26 |
27 | public function start() {
28 | $this->server->start($this->request->pureCli);
29 | }
30 |
31 | public function stop() {
32 | RpcServerApi::stop();
33 | $this->print('Http server was stopped.');
34 | }
35 |
36 | public function reload() {
37 | RpcServerApi::reload();
38 | $this->print('Http server was reloaded.');
39 | }
40 |
41 | public function status() {
42 | $status = RpcServerApi::status();
43 | $status = json_encode($status, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
44 | $this->print($status);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/project/websocket/controller/WebsocketServerController.php:
--------------------------------------------------------------------------------
1 | request->config->websocket['service'];
19 | if (! is_a($serverClass, WebsocketServer::class, true)) {
20 | throw new ConfigException(ConfigException::WEBSOCKET_SERVICE_INVALID);
21 | }
22 | // 构造函数内会挂载RPC客户端, 所以整个公共的呗
23 | $this->server = new $serverClass();
24 | }
25 |
26 | public function start() {
27 | $this->server->start($this->request->pureCli);
28 | }
29 |
30 | public function stop() {
31 | RpcServerApi::stop();
32 | $this->print('Websocket server was stopped.');
33 | }
34 |
35 | public function reload() {
36 | RpcServerApi::reload();
37 | $this->print('Websocket server was reloaded.');
38 | }
39 |
40 | public function status() {
41 | $status = RpcServerApi::status();
42 | $status = json_encode($status, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);
43 | $this->print($status);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/bridge/IdgBatch.php:
--------------------------------------------------------------------------------
1 | value];
22 | }
23 | /** @return Property[] */
24 | public static function getForeignKeyProperties(): array {
25 | return array_map(fn($pid) => self::getPropertyById($pid), static::getForeignKeyPropertyIds());
26 | }
27 | public static function getForeignKeyPropertyByIndex(int $index): Property {
28 | return static::getPropertyById(static::getForeignKeyPropertyIds()[$index]);
29 | }
30 | public static function getForeignKeyValues(): array {
31 | return array_reduce(self::getForeignKeyProperties(), fn($map, $p) => $map + [$p->field->getName() => $this->{$p->name}], []);
32 | }
33 | public static function getTableProperty(): Property {
34 | return static::getPropertyById(ExternalPropertyId::TableId->value);
35 | }
36 | }
--------------------------------------------------------------------------------
/engine/model/validator/ValidatorProperty.php:
--------------------------------------------------------------------------------
1 | format($modelPropertyLabel, $name);
27 | }
28 | } else if (! is_array($value)) {
29 | $value = [$value];
30 | }
31 | $this->value = $value[0];
32 | if (isset($value[1])) {
33 | $errCode = $value[2] ?? 0;
34 | // 如果配置了多语种映射或者异常码, 则实例化为Language, 否则为string
35 | $this->error = is_array($value[1]) || $errCode ? new Language($value[1], $errCode) : $value[1];
36 | }
37 | }
38 |
39 | public function __toString(): string {
40 | return Utility::printable($this->value);
41 | }
42 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/GroupSchema.php:
--------------------------------------------------------------------------------
1 | pushCondition($column);
31 | $this->mergeParams($column->getParams());
32 | } else if (is_string($column) && $column = self::tableWrap($column)) {
33 | $this->pushCondition($column);
34 | } else {
35 | throw (new QueryException(QueryException::GROUP_COLUMN_INVALID))->format(self::printable($column));
36 | }
37 | }
38 | }
39 |
40 | public function __toString(): string {
41 | return implode(',', $this->getConditions());
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/engine/cache/CacheClearable.php:
--------------------------------------------------------------------------------
1 | getMeta($key, true);
20 | if ($meta && $meta['expiry'] > 0 && $meta['expiry'] + $meta['update_time'] < $time) {
21 | $meta = null;
22 | $this->del($key);
23 | // 如果当前的过期了, 则尝试清除其他过期的
24 | foreach ($this->listMeta() as $key => $data) {
25 | if ($data['expiry'] > 0 && $data['expiry'] + $data['update_time'] < $time) {
26 | $this->del([$key]);
27 | }
28 | }
29 | }
30 | return $meta['data'] ?? false;
31 | }
32 |
33 | /**
34 | * 列出缓存元
35 | * @return array
36 | */
37 | abstract public function listMeta(): array;
38 |
39 | /**
40 | * 取缓存元信息
41 | * @param string|array $key
42 | * @param bool $loadData
43 | * @return array|false
44 | */
45 | #[ArrayShape([
46 | 'data' => 'mixed',
47 | 'expiry' => 'int',
48 | 'create_time' => 'int',
49 | 'update_time' => 'int',
50 | ])]
51 | abstract public function getMeta(string|array $key, bool $loadData = false): array|false;
52 | }
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/list/MysqlGroupByParser.php:
--------------------------------------------------------------------------------
1 | offset < $this->statementLength) {
18 | $this->conditions[] = $this->parseWithOffset();
19 | $nextSeparator = $this->preParseOperator();
20 | if (! in_array($nextSeparator, self::$paramSeparators)) {
21 | break;
22 | }
23 | }
24 | }
25 |
26 | public function addItem(MysqlParser $item): void {
27 | $this->conditions[] = $item;
28 | }
29 |
30 | public function toArray(): array {
31 | $conditionsToArray = [];
32 | foreach ($this->conditions as $condition) {
33 | $conditionsToArray[] = $condition->toArray();
34 | }
35 | return [
36 | 'type' => 'list',
37 | 'class' => 'group',
38 | 'conditions' => $conditionsToArray,
39 | ];
40 | }
41 |
42 | public function __toString(): string {
43 | $columns = implode(',', $this->conditions);
44 | return $columns;
45 | }
46 |
47 | protected static function queueProperty(): string {
48 | return 'conditions';
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/engine/db/active/CacheEngine.php:
--------------------------------------------------------------------------------
1 | $modelClass
17 | * @return array
18 | */
19 | public static function encodeKeys(array $data, string $modelClass): array {
20 | return array_reduce(array_keys($data), fn($m, $k) => $m + [$modelClass::getProperty($k)->id, $data[$k]], []);
21 | }
22 |
23 | /**
24 | * @param array $data
25 | * @param class-string $modelClass
26 | * @return array
27 | */
28 | public static function decodeKeys(array $data, string $modelClass): array {
29 | return array_reduce(array_keys($data), fn($m, $k) => $m + [$modelClass::getPropertyById($k)->name => $data[$k]], []);
30 | }
31 |
32 | public static function save(string $cacheKey, array $data): void {
33 | $redis = RedisProxy::new(Dce::$config->cache['redis']['index'], true);
34 | $redis->hMSet($cacheKey, $data);
35 | $redis->expire($cacheKey, 604800);
36 | }
37 |
38 | public static function load(string $cacheKey): array {
39 | return RedisProxy::new(Dce::$config->cache['redis']['index'], true)->hGetAll($cacheKey);
40 | }
41 |
42 | public static function delete(string $cacheKey): void {
43 | RedisProxy::new(Dce::$config->cache['redis']['index'])->del($cacheKey);
44 | }
45 | }
--------------------------------------------------------------------------------
/engine/rpc/RpcHost.php:
--------------------------------------------------------------------------------
1 | setProperties($properties);
41 | }
42 |
43 | /**
44 | * 设置鉴权方案
45 | * @param string $password
46 | * @param array $ipWhiteList
47 | * @param bool $needNative
48 | * @param bool $needLocal
49 | * @return $this
50 | */
51 | public function setAuth(string $password, array $ipWhiteList = [], bool $needNative = false, bool $needLocal = false): self {
52 | $this->needNative = $needNative;
53 | $this->needLocal = $needLocal;
54 | $this->ipWhiteList = $ipWhiteList;
55 | $this->password = $password;
56 | return $this;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/engine/project/request/RawRequest.php:
--------------------------------------------------------------------------------
1 | rawData;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/engine/storage/redis/RedisProxyPool.php:
--------------------------------------------------------------------------------
1 | setConfigs(Dce::$config->redis);
19 | $this->redis = self::$pool->fetch();
20 |
21 | $barrier = Barrier::make();
22 | $exceptions = [];
23 | PdoDbConnector::registerCoroutineAutoReleaseOrHandle(
24 | self::$pool->retryableContainer(function() use($index, $noSerialize) {
25 | parent::__construct($index, $noSerialize);
26 | PdoDbConnector::registerCoroutineAutoReleaseOrHandle(Coroutine::getCid(), false, 'redis');
27 | }, $exceptions, $barrier), type: 'redis'
28 | );
29 | Barrier::wait($barrier);
30 | if ($exceptions) {
31 | self::$pool = null;
32 | throw array_pop($exceptions);
33 | }
34 | }
35 |
36 | public function __destruct() {
37 | $redis = $this->redis;
38 | parent::__destruct();
39 | // 必须最后put,否则高并发下,put回了pool但redis属性未释放,可能导致下述异常:
40 | // Socket#76 has already been bound to another coroutine#19, reading of the same socket in coroutine#24 at the same time is not allowed
41 | self::$pool?->put($redis);
42 | }
43 | }
--------------------------------------------------------------------------------
/project/dce/controller/DceController.php:
--------------------------------------------------------------------------------
1 | print("\n你正在cli模式以空路径请求Dce接口");
16 | }
17 |
18 | #[Node('empty/connection', ['websocket', 'tcp', 'udp'])]
19 | public function connection() {
20 | $this->assign('info', '恭喜!服务端收到了你的消息并给你作出了回应');
21 | $this->response();
22 | }
23 |
24 | #[Node('empty/http/ajax')]
25 | public function ajax() {
26 | $this->assign('info', '请求成功,祝你愉快 (*^▽^*)');
27 | }
28 |
29 | #[Node('empty/http', ['get', 'post', 'put', 'delete', 'options', 'head'])]
30 | public function http() {
31 | $this->response('
32 |
33 |
34 |
35 | 欢迎使用Dce (*´▽`)ノノ
36 |
37 |
38 | 欢迎使用Dce (*´▽`)ノノ
39 | 这是Dce默认HTTP响应页,当你看到它时,表示你的Dce框架成功运行。
40 | 本页面仅在你未配置根路径节点时才会显示,如果你配置了根节点,则会渲染显示该节点控制器的响应内容。
41 | 你可以通过`omissible_path`属性实现根节点效果,参考下述示例。
42 |
43 | return [
44 | [
45 | "path" => "home",
46 | "omissible_path" => true,
47 | "controller" => "IndexController->index",
48 | ],
49 | ];
50 |
51 | 上述配置定义了名为`home`的项目的节点配置,通过设置`omissible_path`为`true`实现可省略路径访问,即你可以通过`http://127.0.0.1/`路径请求`IndexController->index`控制器方法
52 |
53 | ');
54 | }
55 | }
--------------------------------------------------------------------------------
/engine/db/entity/FieldType.php:
--------------------------------------------------------------------------------
1 | 0 ? $length : (match ($this) {
39 | self::Int => [10, 11],
40 | self::Tinyint => [3, 4],
41 | self::Smallint => [5, 6],
42 | self::Mediumint => [8, 8],
43 | self::Bigint => [20, 20],
44 | default => [0, 0],
45 | })[(int) ! $isUnsigned];
46 | }
47 |
48 | public function isNumeric(): bool {
49 | return in_array($this, [self::Int, self::Tinyint, self::Smallint, self::Mediumint, self::Bigint, self::Decimal, self::Float, self::Double]);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/engine/db/query/builder/statement/InsertSelectStatement.php:
--------------------------------------------------------------------------------
1 | tableSchema = $tableSchema;
19 | $this->insertSelectSchema = $insertSelectSchema;
20 | $this->ignoreOrReplace = $ignoreOrReplace;
21 | $this->valid();
22 | $this->mergeParams($this->insertSelectSchema->getParams());
23 | $this->logHasSubQuery($this->insertSelectSchema->hasSubQuery());
24 | }
25 |
26 | public function __toString(): string {
27 | $insertSql = $this->ignoreOrReplace ? 'INSERT IGNORE' : (null === $this->ignoreOrReplace ? 'INSERT' : 'REPLACE');
28 | $sql = "{$insertSql} INTO {$this->tableSchema} {$this->insertSelectSchema}";
29 | return $sql;
30 | }
31 |
32 | protected function valid(): void {
33 | if ($this->tableSchema->isEmpty()) {
34 | throw new QueryException(QueryException::INSERT_TABLE_NOT_SPECIFIED);
35 | }
36 | if ($this->insertSelectSchema->isEmpty()) {
37 | throw new QueryException(QueryException::SELECT_TABLE_NOT_SPECIFIED);
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/statement/MysqlColumnItemParser.php:
--------------------------------------------------------------------------------
1 | field = $this->parseWithOffset();
21 | if (! $this->field instanceof MysqlFieldParser || ! in_array($this->field->field, self::$columnWildcards)) {
22 | // 非通配符才可能有别名
23 | $this->alias = $this->preParseAlias();
24 | }
25 | }
26 |
27 | /**
28 | * 提取聚合函数
29 | * @return MysqlFunctionParser[]
30 | */
31 | public function extractAggregates(): array {
32 | $aggregates = [];
33 | $aggregates = array_merge($aggregates, $this->field->extractAggregates());
34 | return $aggregates;
35 | }
36 |
37 | public function toArray(): array {
38 | return [
39 | 'type' => 'statement',
40 | 'statement' => 'column',
41 | 'field' => $this->field->toArray(),
42 | 'alias' => $this->alias,
43 | ];
44 | }
45 |
46 | public function __toString(): string {
47 | $field = $this->field;
48 | if ($this->alias) {
49 | $field .= " AS {$this->alias}";
50 | }
51 | return $field;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/project/dce/service/server/Connection.php:
--------------------------------------------------------------------------------
1 | initialNode = $initialNode;
27 | $this->session = $session;
28 | $swRequest && $this->swRequest = $swRequest;
29 | return $this;
30 | }
31 |
32 | public function onRequest(): bool {
33 | return RequestManager::current()?->fd === $this->fd;
34 | }
35 |
36 | public function destroy(): void {
37 | Sington::destroy(self::class, $this->fd);
38 | }
39 |
40 | public static function from(int $fd, ServerMatrix $server = null): self {
41 | $instance = Sington::generated(self::class, $fd);
42 | // 即便前个fd的Connection对象未被清除也没关系,因为后续setProps时会覆盖掉旧的属性
43 | is_string($instance) && $instance = Sington::logInstance($instance, new self($fd, $server));
44 | return $instance;
45 | }
46 |
47 | public static function exists(int $fd): self|null {
48 | return is_string($instance = Sington::generated(self::class, $fd)) ? null : $instance;
49 | }
50 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/UpdateSchema.php:
--------------------------------------------------------------------------------
1 | columns = $columns = array_keys($data);
19 |
20 | foreach ($this->columns as $k=>$column) {
21 | $columnName = self::tableWrap($column);
22 | ! $columnName && throw (new QueryException(QueryException::COLUMN_INVALID))->format($column);
23 | $this->columns[$k] = $columnName;
24 | }
25 |
26 | foreach ($columns as $k=>$column) {
27 | $value = $data[$column] ?? null;
28 | if ($value instanceof RawBuilder) {
29 | $this->sqlPackage[] = "{$this->columns[$k]}=$value";
30 | $this->mergeParams($value->getParams());
31 | } else if ($value instanceof SelectStatement) {
32 | $this->sqlPackage[] = "{$this->columns[$k]}=($value)";
33 | $this->mergeParams($value->getParams());
34 | $this->logHasSubQuery(true);
35 | } else {
36 | $this->sqlPackage[] = "{$this->columns[$k]}=?";
37 | $this->pushParam($value);
38 | }
39 | }
40 |
41 | $this->pushCondition($data);
42 | }
43 |
44 | public function __toString(): string {
45 | return implode(',', $this->sqlPackage);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/statement/MysqlWhenParser.php:
--------------------------------------------------------------------------------
1 | when = $this->parseWithOffset(null);
21 | $then = $this->preParseWord();
22 | if (! $then || 'THEN' !== strtoupper($then)) {
23 | throw new StatementParserException(StatementParserException::THEN_MISSING);
24 | }
25 | $this->then = $this->parseWithOffset(null);
26 | }
27 |
28 | /**
29 | * 提取聚合函数
30 | * @return MysqlFunctionParser[]
31 | */
32 | public function extractAggregates(): array {
33 | $aggregates = [];
34 | $aggregates = array_merge($aggregates, $this->when->extractAggregates());
35 | $aggregates = array_merge($aggregates, $this->then->extractAggregates());
36 | return $aggregates;
37 | }
38 |
39 | public function toArray(): array {
40 | return [
41 | 'type' => 'statement',
42 | 'statement' => 'when',
43 | 'when' => $this->when->toArray(),
44 | 'then' => $this->then->toArray(),
45 | ];
46 | }
47 |
48 | public function __toString(): string {
49 | $when = "WHEN {$this->when} THEN {$this->then}";
50 | return $when;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/list/MysqlOrderByParser.php:
--------------------------------------------------------------------------------
1 | offset < $this->statementLength) {
19 | $this->conditions[] = MysqlOrderByConditionParser::build($this->statement, $this->offset);
20 | $nextSeparator = $this->preParseOperator();
21 | if (! in_array($nextSeparator, self::$paramSeparators)) {
22 | break;
23 | }
24 | }
25 | }
26 |
27 | public function addItem(MysqlParser $item): void {
28 | if ($item instanceof MysqlOrderByConditionParser) {
29 | $this->conditions[] = $item;
30 | }
31 | }
32 |
33 | public function toArray(): array {
34 | $conditionsToArray = [];
35 | foreach ($this->conditions as $condition) {
36 | $conditionsToArray[] = $condition->toArray();
37 | }
38 | return [
39 | 'type' => 'list',
40 | 'class' => 'order',
41 | 'conditions' => $conditionsToArray,
42 | ];
43 | }
44 |
45 | public function __toString(): string {
46 | $columns = implode(',', $this->conditions);
47 | return $columns;
48 | }
49 |
50 | protected static function queueProperty(): string {
51 | return 'conditions';
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/engine/project/render/XmlRenderer.php:
--------------------------------------------------------------------------------
1 | header('Content-Type', 'text/xml; charset=utf-8');
18 | }
19 |
20 | /** @inheritDoc */
21 | protected function rendering(Controller $controller, mixed $data): string {
22 | return self::arrayToXml(false === $data ? $controller->getAllAssignedStatus() : $data);
23 | }
24 |
25 | /**
26 | * 简单数组转XML
27 | * @param array $data
28 | * @param SimpleXMLElement|null $xmlElement
29 | * @return string|null
30 | */
31 | private static function arrayToXml(array $data, SimpleXMLElement|null $xmlElement = null): string|null {
32 | $isTopLevel = false;
33 | if (null === $xmlElement) {
34 | $isTopLevel = true;
35 | $xmlElement = new SimpleXMLElement('');
36 | }
37 | foreach ($data as $key => $value) {
38 | if (is_int($key)){
39 | $key = "item";
40 | }
41 | if (is_array($value)) {
42 | $childXmlElement = $xmlElement->addChild($key);
43 | self::arrayToXml($value, $childXmlElement);
44 | } else {
45 | $xmlElement->addChild($key, htmlspecialchars($value));
46 | }
47 | }
48 | return $isTopLevel ? $xmlElement->asXML(): null;
49 | }
50 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/InsertSelectSchema.php:
--------------------------------------------------------------------------------
1 | format(self::printable($column));
25 | }
26 | $this->columns[] = $columnsName;
27 | }
28 | }
29 | if (! ($isSelect = $selectStatement instanceof SelectStatement) && ! $selectStatement instanceof RawBuilder) {
30 | throw (new QueryException(QueryException::SELECT_STRUCT_INVALID))->format(self::printable($selectStatement));
31 | }
32 | $this->mergeParams($selectStatement->getParams());
33 | $this->pushCondition($selectStatement);
34 | $this->logHasSubQuery($isSelect);
35 | }
36 |
37 | public function getColumns() {
38 | return $this->columns;
39 | }
40 |
41 | public function __toString(): string {
42 | $columnsSql = empty($this->columns) ? '' : '(' .implode(',', $this->columns). ') ';
43 | $selectSql = current($this->getConditions());
44 | return "{$columnsSql}{$selectSql}";
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/WindowSchema.php:
--------------------------------------------------------------------------------
1 | setPartition($partition);
23 | $this->mergeParams($parts[1]->getParams());
24 | }
25 | if ($order) {
26 | $parts[2] = new OrderSchema();
27 | $parts[2]->addOrder($order, null, false);
28 | $this->mergeParams($parts[2]->getParams());
29 | }
30 | if ($frame) {
31 | $parts[3] = new FrameSchema();
32 | $parts[3]->setFrame($frame);
33 | $this->mergeParams($parts[3]->getParams());
34 | }
35 | $this->pushCondition([$name, $parts]);
36 | }
37 |
38 | public function __toString(): string {
39 | return implode(', ', array_map(fn($c) => "$c[0] AS (" .
40 | implode(' ', array_filter([$c[1][0], $c[1][1] ? "PARTITION BY {$c[1][1]}" : false, $c[1][2] ? "ORDER BY {$c[1][2]}" : false, $c[1][3]]))
41 | . ')', $this->getConditions()));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/FrameSchema.php:
--------------------------------------------------------------------------------
1 |
18 | * string 字符串型frame,将会进行正则校验,有效值如:ROWS UNBOUNDED PRECEDING; ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING
19 | * RawBuilder 不会进行正则校验,如:raw('ROWS UNBOUNDED PRECEDING', false); raw('ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING', false)
20 | *
21 | * @throws QueryException
22 | */
23 | public function setFrame(string|RawBuilder $frame): void {
24 | if (is_string($frame)) {
25 | ! preg_match(self::VALID_REG, $frame) && throw new QueryException(QueryException::WINDOW_FRAME_INVALID);
26 | $this->pushCondition($frame);
27 | } else {
28 | $this->pushCondition($frame);
29 | $this->mergeParams($frame->getParams());
30 | }
31 | }
32 |
33 | public function __toString(): string {
34 | return (string) $this->getConditions()[0];
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/engine/model/validator/ending/UniqueValidator.php:
--------------------------------------------------------------------------------
1 | model instanceof ActiveRecord) {
18 | if (! $value) return null;
19 | $combined = $this->getProperty('combined');
20 | $combinedNames = $combined->value ?? [];
21 | $whereConditions = [[$this->model::toDbKey($this->modelPropertyName), '=', $value]];
22 | foreach ($combinedNames as $combinedName)
23 | $whereConditions[] = [$combinedName, '=', $this->model->{$this->model::toModelKey($combinedName)}];
24 | $rows = $this->model::query()->where($whereConditions)->limit(2)->select();
25 | if (! empty($rows)) { // 如果找到了记录
26 | if ($this->model->isCreateByQuery()) { // 如果创建自查询, 且如果查询到的数据大于两条, 或查出的主键与当前主键不同, 则表示有重复
27 | if (count($rows) > 1 || ! empty(array_diff_assoc($rows[0]->getPkValues(), $this->model->getPkValues())))
28 | $this->addError($this->getGeneralError($combined->error ?? null, lang(ValidatorException::CANNOT_REPEAT)));
29 | } else { // 如果非创建自查询, 则为插入, 则只要有查到即为重复的
30 | $this->addError($this->getGeneralError($combined->error ?? null, lang(ValidatorException::CANNOT_REPEAT)));
31 | }
32 | }
33 | } else {
34 | $this->addError(lang(ValidatorException::NOT_SUPPORT_UNIQUE));
35 | }
36 |
37 | return $this->getError();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/engine/db/proxy/SimpleTransaction.php:
--------------------------------------------------------------------------------
1 | proxy) && throw new TransactionException(TransactionException::REPEATED_OPEN);
23 | }
24 |
25 | /**
26 | * 按代理匹配事务实例
27 | * @param DbProxy $proxy
28 | * @return static|null
29 | */
30 | private static function proxyMatch(DbProxy $proxy): static|null {
31 | foreach (self::$pond as $transaction)
32 | if ($transaction->proxy === $proxy) return $transaction;
33 | return null;
34 | }
35 |
36 | /**
37 | * 实例化一个事务对象,并递增进入计数
38 | * @param SimpleDbProxy $proxy
39 | * @return static
40 | */
41 | public static function begin(SimpleDbProxy $proxy): self {
42 | $instance = self::proxyMatch($proxy) ?? new self($proxy);
43 | $instance->entries ++;
44 | return $instance;
45 | }
46 |
47 | /**
48 | * 检测并尝试开启连接事务
49 | * @param SimpleDbProxy $proxy
50 | * @param DbConnector $connector
51 | */
52 | public static function tryBegin(SimpleDbProxy $proxy, DbConnector $connector): void {
53 | $transaction = self::proxyMatch($proxy);
54 | if ($transaction && ! isset($transaction->connector)) {
55 | $transaction->markConnector($connector);
56 | $connector->begin();
57 | }
58 | }
59 | }
--------------------------------------------------------------------------------
/engine/sharding/middleware/MiddlewareException.php:
--------------------------------------------------------------------------------
1 | %s > id_column'])]
19 | public const CONFIG_ID_COLUMN_EMPTY = 1801;
20 |
21 | #[Language(['缺少分库配置 %s > %s > sharding_column'])]
22 | public const CONFIG_SHARDING_COLUMN_EMPTY = 1802;
23 |
24 | #[Language(['分库配置错误, 未配置 %s 表内容切分依据字段'])]
25 | public const CONFIG_TABLE_SHARDING_RULE_EMPTY = 1803;
26 |
27 | // 运行时异常
28 | #[Language(['非按模分库禁止连表查询'])]
29 | public const NO_MOD_SHARDING_NO_JOINT = 1820;
30 |
31 | #[Language(['未开启分库连表查询 (可以设置allow_joint=true开启)'])]
32 | public const ALLOW_JOINT_NOT_OPEN = 1821;
33 |
34 | #[Language(['分库配置错误, 未在储存数据中找到作为ID基因的字段 %s'])]
35 | public const GENE_COLUMN_NOT_FOUND = 1822;
36 |
37 | #[Language(['禁止更新分库依据字段,若允许请开启 cross_update 参数。该操作较危险,请谨慎。(建议自己实现跨库迁移功能)'])]
38 | public const OPEN_CROSS_UPDATE_TIP = 1823;
39 |
40 | #[Language(['记录插入失败,获取新ID失败'])]
41 | public const INSERT_FAILED_NO_ID = 1824;
42 |
43 | #[Language(['分库插入错误,未配置idTag时需手动指定待插入ID %s'])]
44 | public const INSERT_ID_NOT_SPECIFIED = 1825;
45 |
46 | #[Language(['分库依据字段 %s 未指定有效值'])]
47 | public const SHARDING_COLUMN_NOT_SPECIFIED = 1826;
48 |
49 | #[Language(['表 %s 中不存在分表依据字段 %s,或该字段有无效值,无法进行迁移更新'])]
50 | public const SHARDING_VALUE_NOT_SPECIFIED = 1827;
51 |
52 | #[Language(['跨库更新表 %s 时需指定idColumn字段 %s 的值'])]
53 | public const UP_ID_VALUE_NOT_SPECIFIED = 1828;
54 |
55 | #[Language(['跨库更新表 %s 时需指定shardingColumn字段 %s 的值'])]
56 | public const UP_SHARDING_VALUE_NOT_SPECIFIED = 1829;
57 | }
58 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/statement/MysqlOrderByConditionParser.php:
--------------------------------------------------------------------------------
1 | field = $this->parseWithOffset();
22 | $sort = $this->preParseWord();
23 | if ($sort) {
24 | $sortUpper = strtoupper($sort);
25 | if (in_array($sortUpper, ['DESC', 'ASC'])) {
26 | $this->sort = $sortUpper;
27 | } else {
28 | $this->offset -= mb_strlen($sort);
29 | }
30 | }
31 | if ('ASC' !== $this->sort) {
32 | $this->isAsc = false;
33 | }
34 | }
35 |
36 | /**
37 | * 提取聚合函数
38 | * @return MysqlFunctionParser[]
39 | */
40 | public function extractAggregates(): array {
41 | $aggregates = [];
42 | $aggregates = array_merge($aggregates, $this->field->extractAggregates());
43 | return $aggregates;
44 | }
45 |
46 | public function toArray(): array {
47 | return [
48 | 'type' => 'statement',
49 | 'statement' => 'order',
50 | 'field' => $this->field->toArray(),
51 | 'sort' => $this->sort,
52 | 'is_asc' => $this->isAsc,
53 | ];
54 | }
55 |
56 | public function __toString(): string {
57 | $order = $this->field;
58 | if (! $this->isAsc) {
59 | $order .= "{$this->field} DESC";
60 | }
61 | return $order;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/client/IdgClientTime.php:
--------------------------------------------------------------------------------
1 | timeId)) {
23 | throw new IdgException(IdgException::BASE_TIME_ID_MISSING);
24 | }
25 | }
26 |
27 | /**
28 | * 生成TimeId
29 | * @param IdgBatch $batch
30 | * @param int $base
31 | * @param int $nextBit
32 | * @return int
33 | */
34 | protected static function generateBatch(IdgBatch $batch, int $base, int $nextBit): int {
35 | $base = parent::generateBatch($batch, $base, $nextBit);
36 | // uid左移service比特位 (非bc库还是够用100年的吧)
37 | $base += $batch->timeId << ($nextBit + $batch->batchBitWidth);
38 | return $base;
39 | }
40 |
41 | /**
42 | * 解析TimeId
43 | * @param int $id
44 | * @return IdgBatch
45 | */
46 | public function parse(int $id): IdgBatch {
47 | $batch = parent::parse($id);
48 | $unParsedId = $batch->batchId;
49 | if (! empty($this->batch->batchBitWidth)) {
50 | $batch->batchId = self::parsePart($unParsedId, $this->batch->batchBitWidth);
51 | }
52 | // TimeId为解析后剩余部分
53 | $batch->timeId = $unParsedId;
54 | return $batch;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/engine/base/Lock.php:
--------------------------------------------------------------------------------
1 | procLockInit();
14 | $this->distributedLockInit();
15 | }
16 |
17 | /**
18 | * 跨进程协程锁初始化 (实例化跨进程对象)
19 | */
20 | protected function procLockInit(): void {}
21 |
22 | /**
23 | * 加跨进程协程锁 (悲观, 自旋, 不可重入)
24 | * @param string $identification 锁标识
25 | * @param int $maximum 跨进程可重入次数
26 | * @return bool
27 | */
28 | public function procLock(string $identification, int $maximum = 1): bool {
29 | return true;
30 | }
31 |
32 | /**
33 | * 解跨进程协程锁
34 | * @param string $identification
35 | */
36 | public function procUnlock(string $identification): void {}
37 |
38 | /**
39 | * 加协程锁 (悲观, 自旋, 不可重入)
40 | * @param string $identification 锁标识
41 | * @param int $maximum 跨进程可重入次数
42 | * @return bool
43 | */
44 | public function coLock(string $identification, int $maximum = 1): bool {
45 | return true;
46 | }
47 |
48 | /**
49 | * 解协程锁
50 | * @param string $identification
51 | */
52 | public function coUnlock(string $identification): void {}
53 |
54 | /**
55 | * 分布式锁初始化
56 | */
57 | protected function distributedLockInit(): void {}
58 |
59 | /**
60 | * 分布式锁加锁
61 | * @return bool
62 | */
63 | public function distributedLock(): bool {
64 | return true;
65 | }
66 |
67 | /**
68 | * 分布式锁解锁
69 | */
70 | public function distributedUnlock(): void {}
71 |
72 | /**
73 | * 实例化一个并发锁对象
74 | * @return static
75 | */
76 | public static function init(): static {
77 | $lockClass = Dce::$config->lockClass ?? (SwooleUtility::inSwoole() ? SwooleLock::class : self::class);
78 | return new $lockClass;
79 | }
80 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/OrderSchema.php:
--------------------------------------------------------------------------------
1 | format(self::printable($condition[0] ?? ''));
26 | $order = strtoupper(trim($condition[1] ?? ''));
27 | $this->pushCondition($column . (in_array($order, ['ASC', 'DESC']) ? " {$order}" : ''));
28 | $isRaw && $this->mergeParams($column->getParams());
29 | } else if (($isRaw = $condition instanceof RawBuilder) || is_string($condition) && $column = self::tableWrap($condition)) {
30 | $this->pushCondition($isRaw ? $condition : $column);
31 | $isRaw && $this->mergeParams($condition->getParams());
32 | } else {
33 | throw (new QueryException(QueryException::ORDER_BY_INVALID))->format(self::printable($condition));
34 | }
35 | }
36 | }
37 |
38 | public function __toString(): string {
39 | return implode(',', $this->getConditions());
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/MysqlListParser.php:
--------------------------------------------------------------------------------
1 | extractAggregates());
23 | return $aggregates;
24 | }
25 |
26 | public function current(): MysqlParser {
27 | return $this->getQueue()[$this->queueOffset];
28 | }
29 |
30 | public function next(): void {
31 | $this->queueOffset ++;
32 | }
33 |
34 | public function key(): int {
35 | return $this->queueOffset;
36 | }
37 |
38 | public function valid(): bool {
39 | return $this->queueOffset < count($this->getQueue());
40 | }
41 |
42 | public function rewind(): void {
43 | $this->queueOffset = 0;
44 | }
45 |
46 | public function delItem(int $offset, int $length = 1): void {
47 | array_splice($this->getQueue(), $offset, $length);
48 | }
49 |
50 | /**
51 | * @return MysqlParser[]
52 | */
53 | private function & getQueue(): array {
54 | $property = static::queueProperty();
55 | return $this->$property;
56 | }
57 |
58 | public static function build(string $statement, int & $offset = 0): static {
59 | $instance = new static($statement, $offset);
60 | $instance->parse();
61 | return $instance;
62 | }
63 |
64 | abstract protected function parse(): void;
65 |
66 | abstract public function addItem(MysqlParser $item): void;
67 |
68 | abstract protected static function queueProperty(): string;
69 | }
70 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/TableSchema.php:
--------------------------------------------------------------------------------
1 | tablePack($tableName);
17 | $this->mergeParams($params);
18 | $this->pushCondition([
19 | 'table' => $tableSql,
20 | 'alias' => self::tableWrap($alias),
21 | ]);
22 | return $this;
23 | }
24 |
25 | public function tablePack(string|RawBuilder|SelectStatement $table) {
26 | $params = [];
27 | if (is_string($table) && $tableName = self::tableWrap($table)) {
28 | $tableSql = $tableName;
29 | } else if ($table instanceof RawBuilder) {
30 | $tableSql = "$table";
31 | $params = $table->getParams();
32 | } else if ($table instanceof SelectStatement) {
33 | $tableSql = "($table)";
34 | $params = $table->getParams();
35 | $this->logHasSubQuery(true);
36 | } else {
37 | throw new QueryException(QueryException::TABLE_INVALID);
38 | }
39 | return [$tableSql, $params];
40 | }
41 |
42 | public function __toString(): string {
43 | $tableParts = [];
44 | foreach ($this->getConditions() as $table) {
45 | $tableParts[] = $table['table'] . ($table['alias'] ? " AS {$table['alias']}" : '');
46 | }
47 | $tableSql = implode(',', $tableParts);
48 | return $tableSql;
49 | }
50 |
51 | public function getName(): string {
52 | return trim($this->getConditions()[0]['table'] ?? '', ' `');
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/engine/model/validator/checker/NumberValidator.php:
--------------------------------------------------------------------------------
1 | addError($this->getGeneralError(null, lang(ValidatorException::INVALID_NUMBER)));
30 | } else {
31 | $negative = $this->getProperty('negative');
32 | if (! ($negative->value ?? null) && $value < 0) {
33 | $this->addError($this->getGeneralError($negative->error ?? null, lang(ValidatorException::CANNOT_BE_NEGATIVE)));
34 | }
35 |
36 | $decimal = $this->getProperty('decimal');
37 | if (! ($decimal->value ?? null) && ! ctype_digit(ltrim((string) $value, '-'))) {
38 | $this->addError($this->getGeneralError($decimal->error ?? null, lang(ValidatorException::CANNOT_BE_FLOAT)));
39 | }
40 |
41 | $min = $this->getProperty('min');
42 | if ($min && $value < $min->value) {
43 | $this->addError($this->getGeneralError($min->error, lang(ValidatorException::CANNOT_SMALL_THAN)));
44 | }
45 |
46 | $max = $this->getProperty('max');
47 | if ($max && $value > $max->value) {
48 | $this->addError($this->getGeneralError($max->error, lang(ValidatorException::CANNOT_LARGE_THAN)));
49 | }
50 | }
51 |
52 | return $this->getError();
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/engine/db/query/builder/RawBuilder.php:
--------------------------------------------------------------------------------
1 | $placeholder) {
25 | if ('?' === $placeholder) {
26 | $isNamingPlaceholder = false;
27 | } else {
28 | $isQuestionPlaceholder = false;
29 | $k = $placeholder;
30 | $statement = preg_replace("/$placeholder\b/", '?', $statement, 1);
31 | }
32 | if (! key_exists($k, $params) && (($k = ltrim($k, ':')) && ! key_exists($k, $params))) {
33 | throw (new QueryException(QueryException::PLACEHOLDER_NOT_MATCH))->format($k);
34 | }
35 | $convertedParams[] = $params[$k];
36 | }
37 | if (! $isQuestionPlaceholder && ! $isNamingPlaceholder) {
38 | throw new QueryException(QueryException::NOT_ALLOW_MIXED_PLACEHOLDER);
39 | }
40 | $this->mergeParams($convertedParams);
41 | }
42 | $this->statement = $statement;
43 | $this->autoParenthesis = $autoParenthesis;
44 | }
45 |
46 | protected function valid(): void {}
47 |
48 | public function __toString(): string {
49 | return sprintf($this->autoParenthesis ? '(%s)' : '%s', $this->statement);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/engine/pool/TcpPool.php:
--------------------------------------------------------------------------------
1 | port ?? 0) < 1 ? SWOOLE_SOCK_UNIX_STREAM : SWOOLE_SOCK_TCP);
19 | if (! $connection->connect($config->host, $config->port ?? 0, $config->timeout ?? -1)) {
20 | throw new PoolException($connection->errMsg, $connection->errCode);
21 | }
22 | $this->initTick($connection);
23 | return $connection;
24 | }
25 |
26 | private function initTick(Client $connection): void {
27 | $this->tickMapping[spl_object_id($connection)] = Timer::tick(30000, function() use($connection) {
28 | $lastFetchTime = $this->getProduct($connection)->lastFetch ?? 0;
29 | if (time() - $lastFetchTime >= 28) {
30 | // 如果到了闹铃点, 且距上次取连接已过了闹铃间隔时间(未过则不必发, 因为取连接则肯定用来send过业务数据了, send数据能覆盖ping功能), 则发送ping包
31 | $connection->send(0);
32 | }
33 | });
34 | }
35 |
36 | public function destroyProduct(object $object): void {
37 | $objId = spl_object_id($object);
38 | Timer::clear($this->tickMapping[$objId]);
39 | unset($this->tickMapping[$objId]);
40 | }
41 |
42 | /** @inheritDoc */
43 | public function fetch(array $config = []): Client {
44 | return $this->get($config);
45 | }
46 |
47 | /**
48 | * 取池子实例
49 | * @param string ...$identities
50 | * @return static
51 | */
52 | public static function inst(string ... $identities): static {
53 | return parent::getInstance(TcpPoolProductionConfig::class, ... $identities);
54 | }
55 |
56 | protected function retryable(Throwable $throwable): bool {
57 | return false;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/engine/project/Project.php:
--------------------------------------------------------------------------------
1 | path, ProjectManager::$systemProjectRoot) && $this->isSystematic = true;
34 | }
35 |
36 | public function setNodeTree(NodeTree $nodeTree) {
37 | $this->nodeTree = $nodeTree;
38 | }
39 |
40 | /**
41 | * 取项目配置, 若配置为初始化过, 则先初始化
42 | * @return DceConfig
43 | */
44 | public function getConfig(): DceConfig {
45 | if (! isset($this->config)) {
46 | $this->config = ConfigManager::getProjectConfig($this);
47 | }
48 | return $this->config;
49 | }
50 |
51 | /**
52 | * 取未合并公共配置的项目配置
53 | * @return DceConfig
54 | */
55 | public function getPureConfig(): DceConfig {
56 | if (! isset($this->pureConfig)) {
57 | $this->pureConfig = ConfigManager::getPureProjectConfig($this);
58 | }
59 | return $this->pureConfig;
60 | }
61 |
62 | /**
63 | * 取项目根节点
64 | * @return Node
65 | */
66 | public function getRootNode(): Node {
67 | return $this->nodeTree->getFirstNode();
68 | }
69 |
70 | /**
71 | * 扩展属性
72 | * @param string $key
73 | * @param mixed $value
74 | */
75 | public function extendProperty(string $key, mixed $value) {
76 | $this->extra[$key] = $value;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/engine/loader/attr/SingMatrix.php:
--------------------------------------------------------------------------------
1 | |callable $class
17 | * @param mixed ...$args
18 | * @return T
19 | */
20 | public static function gen(string|callable $class, mixed ... $args): mixed {
21 | $isCallable = is_callable($class);
22 | $id = static::genInstanceId($isCallable ? array_shift($args) : $class, $args);
23 | return key_exists($id, static::$mapping) ? static::$mapping[$id] : static::logInstance($id, $isCallable ? call_user_func($class) : self::new($class, ... $args));
24 | }
25 |
26 | /**
27 | * @template T
28 | * @param string $id
29 | * @param T $instance
30 | * @return T
31 | */
32 | public static function logInstance(string $id, mixed $instance): mixed {
33 | return static::$mapping[$id] = $instance;
34 | }
35 |
36 | /**
37 | * @template T
38 | * @param class-string $class
39 | * @param mixed ...$args
40 | * @return T|string
41 | */
42 | public static function generated(string $class, mixed ... $args): mixed {
43 | $id = static::genInstanceId($class, $args);
44 | return key_exists($id, static::$mapping) ? static::$mapping[$id] : $id;
45 | }
46 |
47 | public static function destroy(string $class, mixed ... $args): void {
48 | unset(static::$mapping[static::genInstanceId($class, $args)]);
49 | }
50 |
51 | protected abstract static function genInstanceId(string $class, array $args): string;
52 |
53 | /**
54 | * @template T
55 | * @param class-string $class
56 | * @param mixed ...$args
57 | * @return T
58 | */
59 | public static function new(string $class, mixed ... $args): mixed {
60 | return new $class(... $args);
61 | }
62 | }
--------------------------------------------------------------------------------
/engine/cache/engine/RedisCache.php:
--------------------------------------------------------------------------------
1 | config['index'])->get(self::genKey($key));
21 | }
22 |
23 | /** @inheritDoc */
24 | public function exists(string|array $key): bool {
25 | return RedisProxy::new($this->config['index'])->exists(self::genKey($key));
26 | }
27 |
28 | /** @inheritDoc */
29 | public function set(array|string $key, mixed $value, int $expiry = null): bool {
30 | return RedisProxy::new($this->config['index'])->set(self::genKey($key), $value, $expiry);
31 | }
32 |
33 | public function touch(array|string $key, int $expiry = 0): bool {
34 | return RedisProxy::new($this->config['index'])->expire(self::genKey($key), $expiry);
35 | }
36 |
37 | /** @inheritDoc */
38 | public function inc(array|string $key, float $value = 1): int|float|false {
39 | return RedisProxy::new($this->config['index'])->incrBy(self::genKey($key), $value);
40 | }
41 |
42 | /** @inheritDoc */
43 | public function dec(array|string $key, float $value = 1): int|float|false {
44 | return RedisProxy::new($this->config['index'])->decrBy(self::genKey($key), $value);
45 | }
46 |
47 | /** @inheritDoc */
48 | public function del(array|string $key): bool {
49 | return !! RedisProxy::new($this->config['index'])->del(self::genKey($key));
50 | }
51 |
52 | /** @inheritDoc */
53 | public function clear(): void {
54 | $redis = RedisProxy::new($this->config['index']);
55 | // 直接删掉和当前主机应用绑定的, 几乎都是缓存, 非缓存的全服型数据不会与应用ID绑定
56 | foreach ($redis->keys(Dce::getId() . ':*') as $key) $redis->del($key);
57 | }
58 | }
--------------------------------------------------------------------------------
/project/dce/service/server/ServerApi.php:
--------------------------------------------------------------------------------
1 | stop();
29 | }
30 |
31 | /**
32 | * 重载服务
33 | */
34 | public static function reload(): void {
35 | static::$server->reload();
36 | }
37 |
38 | /**
39 | * 获取服务状态
40 | * @return array
41 | */
42 | public static function status(): array {
43 | return static::$server->status();
44 | }
45 |
46 | /**
47 | * 向Tcp客户端推送消息
48 | * @param int $fd
49 | * @param mixed $value
50 | * @param string|false $path
51 | * @return bool
52 | */
53 | public static function send(int $fd, mixed $value, string|false $path): bool {
54 | return static::$server->send($fd, $value, $path);
55 | }
56 |
57 | /**
58 | * 向Udp客户端推送消息
59 | * @param string $host
60 | * @param int $port
61 | * @param mixed $value
62 | * @param string|false $path
63 | * @return bool
64 | */
65 | public static function sendTo(string $host, int $port, mixed $value, string|false $path): bool {
66 | return static::$server->sendTo($host, $port, $value, $path);
67 | }
68 |
69 | /**
70 | * 向Websocket客户端推送消息
71 | * @param int $fd
72 | * @param mixed $value
73 | * @param string|false $path
74 | * @return bool
75 | */
76 | public static function push(int $fd, mixed $value, string|false $path): bool {
77 | return self::$server->push($fd, $value, $path);
78 | }
79 | }
--------------------------------------------------------------------------------
/project/tcp/service/RawRequestUdp.php:
--------------------------------------------------------------------------------
1 | clientInfo['ip'] = $this->clientInfo['address'];
23 | $this->clientInfo['packet'] = $packet;
24 | }
25 |
26 | /** @inheritDoc */
27 | public function getServer(): ServerMatrix {
28 | return $this->server;
29 | }
30 |
31 | /** @inheritDoc */
32 | public function getRaw(): array {
33 | return $this->clientInfo;
34 | }
35 |
36 | /** @inheritDoc */
37 | public function init(): void {
38 | ['path' => $this->path, 'requestId' => $this->requestId, 'data' => $this->rawData, 'dataParsed' => $this->dataParsed] = $this->unPack($this->clientInfo['packet']);
39 | }
40 |
41 | /** @inheritDoc */
42 | public function supplementRequest(Request $request): void {
43 | $request->rawData = $this->rawData;
44 | $request->request = is_array($this->dataParsed) ? $this->dataParsed : [];
45 | }
46 |
47 | /** @inheritDoc */
48 | public function response(mixed $data, string|false $path): bool {
49 | LogManager::response($this, $data);
50 | return $this->server->sendTo($this->clientInfo['address'], $this->clientInfo['port'], $data, $path . (isset($this->requestId) ? self::REQUEST_SEPARATOR . $this->requestId : ''));
51 | }
52 |
53 | /** @inheritDoc */
54 | public function getClientInfo(): array {
55 | $this->clientInfo['request'] = "$this->method :{$this->clientInfo['server_port']}/$this->path#" . ($this->requestId ?? '');
56 | return $this->clientInfo;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/list/MysqlColumnParser.php:
--------------------------------------------------------------------------------
1 | preParseModifier()) && $this->modifiers[] = $modifier;
22 |
23 | while ($this->offset < $this->statementLength) {
24 | $this->columns[] = MysqlColumnItemParser::build($this->statement, $this->offset);
25 | $nextSeparator = $this->preParseOperator();
26 | if (! in_array($nextSeparator, self::$paramSeparators)) break;
27 | }
28 | }
29 |
30 | /** @return MysqlColumnItemParser */
31 | public function current(): MysqlColumnItemParser {
32 | return parent::current();
33 | }
34 |
35 | public function addItem(MysqlParser $item): void {
36 | $item instanceof MysqlColumnItemParser && $this->columns[] = $item;
37 | }
38 |
39 | public function toArray(): array {
40 | $columnsToArray = [];
41 | foreach ($this->columns as $column)
42 | $columnsToArray[] = $column->toArray();
43 | return [
44 | 'type' => 'list',
45 | 'class' => 'column',
46 | 'modifiers' => $this->modifiers,
47 | 'columns' => $columnsToArray,
48 | ];
49 | }
50 |
51 | public function __toString(): string {
52 | $modifiers = implode(' ', $this->modifiers);
53 | $columns = implode(',', $this->columns);
54 | $modifiers && $columns = "{$modifiers} {$columns}";
55 | return $columns;
56 | }
57 |
58 | protected static function queueProperty(): string {
59 | return 'columns';
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/project/dce/service/cron/TaskIterator.php:
--------------------------------------------------------------------------------
1 | $supplier
15 | * @param callable(T, string|int, int): void $consumer
16 | * @param callable(T, string|int): void $progressLogger
17 | * @param string|int $refCursor
18 | */
19 | public static function batchIterate(callable $supplier, callable $consumer, callable $progressLogger, string|int & $refCursor): void {
20 | $prevHash = null;
21 | // 若取到与前一轮一样的数据,则表示已取完,需退出循环
22 | while (($calcList = call_user_func_array($supplier, [$refCursor])) && $prevHash !== $currentHash = json_encode($calcList[$lastKey = array_key_last($calcList)])) {
23 | $prevHash = $currentHash;
24 | call_user_func_array($consumer, [$calcList, & $refCursor, $lastKey]);
25 | call_user_func_array($progressLogger, [$calcList[$lastKey], & $refCursor]);
26 | }
27 | }
28 |
29 | /**
30 | * @template T
31 | * @param callable(string|int): list $supplier
32 | * @param callable(T, string|int): void $consumer
33 | * @param callable(T, string|int): void $progressLogger
34 | * @param string|int $refCursor
35 | * @param int $stepModToLog
36 | */
37 | public static function batchStepIterate(callable $supplier, callable $consumer, callable $progressLogger, string|int & $refCursor, int $stepModToLog = 16): void {
38 | self::batchIterate($supplier, function(array $calcList, string|int & $refCursor, int $lastKey) use($consumer, $progressLogger, $stepModToLog) {
39 | foreach ($calcList as $k => $item) {
40 | call_user_func_array($consumer, [$item, & $refCursor]);
41 | (($k && ! ($k % $stepModToLog)) && $k !== $lastKey) && call_user_func_array($progressLogger, [$item, & $refCursor]);
42 | }
43 | }, $progressLogger, $refCursor);
44 | }
45 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/SelectSchema.php:
--------------------------------------------------------------------------------
1 | $column) {
23 | $isAutoRaw && is_string($column) && $column = new RawBuilder($column, false);
24 | $this->addColumn($column, is_int($alias) ? null : $alias);
25 | }
26 | }
27 |
28 | public function addColumn(string|RawBuilder $column, string|null $alias = null) {
29 | $columnName = $column;
30 | if (! ($isRaw = $column instanceof RawBuilder) && ! is_numeric($column))
31 | if (! $columnName = self::tableWrap($column, true))
32 | throw (new QueryException(QueryException::SELECT_COLUMN_INVALID))->format(self::printable($column));
33 | $columnName .= $alias ? " $alias" : '';
34 | if (! in_array($columnName, $this->getConditions())) {
35 | $this->pushCondition($columnName);
36 | $isRaw && $this->mergeParams($column->getParams());
37 | }
38 | }
39 |
40 | /**
41 | * 将查询字段转为查询结果的数组键名
42 | * @param string|RawBuilder $column
43 | * @return mixed
44 | */
45 | public function columnToKey(string|RawBuilder $column) {
46 | if (! is_string($column) || ! $columnParts = self::tableNameParse($column)) return $column;
47 | [, $columnName, $alias] = $columnParts;
48 | return $alias ?: $columnName;
49 | }
50 |
51 | public function __toString(): string {
52 | return implode(',', $this->getConditions());
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DCE
2 |
3 | Dce是一款基于PHP8开发的网络编程框架,支持传统Cgi式Web编程及命令行工具编程,也支持Swoole下常驻内存式Web编程与长连接服务器编程,并且设计了一套通用的RCR架构处理所有类型网络编程,让你的应用项目保持清晰整洁,助你轻松编写出易复用、好维护的代码。
4 |
5 | 
6 |
7 | Dce还有很多特色功能,其中最为重量级的是 ~~分库中间件~~,可以让你轻松实现分库查询。除此之外还提供有负载均衡连接池、远程过程调用、ID生成器、并发锁、会话管理器等特色功能,这些功能依赖Swoole,Dce作者也强烈推荐你在Swoole下使用Dce,配合其多进程协程模式,可以将你的服务器性能发挥到极致。
8 |
9 | 当然,除了上述功能外,如模型、校验器、缓存器、事件、查询器、活动记录等这些常规的功能模块也必不可能缺少。
10 |
11 |
12 |
13 | ### 开始使用
14 |
15 |
16 | #### 获取
17 |
18 | ```shell
19 | composer create-project idrunk/dce-app:@dev
20 | ```
21 |
22 | 取用例
23 | ```shell
24 | composer create-project idrunk/dce-app:dev-sample dce-sample
25 | ```
26 |
27 |
28 | #### 使用命令行工具
29 |
30 | 执行一个空命令
31 | ```shell
32 | ./dce
33 | # 或者在windows下执行:
34 | .\dce.bat
35 | # 将响应:
36 | #
37 | # 你正在cli模式以空路径请求Dce接口
38 | ```
39 |
40 | 在有Swoole的Linux下启动Websocket服务器
41 | ```shell
42 | dce websocket start
43 | # 将响应
44 | # Websocket server started with 0.0.0.0:20461.
45 | ```
46 |
47 | JS连接Websocket服务
48 | ```js
49 | const ws = new WebSocket('ws://127.0.0.1:20461');
50 | ws.onopen = () => ws.send('');
51 | ws.onmessage = msg => console.log(msg.data);
52 | // 若连接成功,将在控制台打印出下述消息
53 | /*
54 | ;{"data":{"info":"恭喜!服务端收到了你的消息并给你作出了回应"}}
55 | */
56 | ```
57 |
58 |
59 | #### 使用Redis连接池
60 |
61 | RedisProxy会自动根据环境判定从实例池取还是新建连接,若使用实例池,则会自动取还。
62 | ```php
63 | RedisProxy::new()->set('homepage', 'https://drunkce.com');
64 | ```
65 |
66 |
67 | #### 数据库查询
68 |
69 | 分库查询需要进行分库规则配置,但查询方法和普通查询没区别,所以下述示例也适用于分库查询。
70 | ```php
71 | // 查一条
72 | $row = db('member')->where('mid', 4100001221441)->find();
73 | // db方法为实例化查询器的快捷方法
74 |
75 | // 简单联合查询
76 | $list = db('member', 'm')->join('member_role', 'mr', 'mr.mid = m.mid')->select();
77 |
78 | // 较复杂的嵌套条件查询
79 | $list = db('member')->where([
80 | ['is_deleted', 0],
81 | ['register_time', 'between', ['2021-01-01', '2021-01-31 23:59:59']],
82 | [
83 | ['level', '>', 60],
84 | 'or',
85 | ['vip', '>', 1],
86 | ],
87 | ['not exists', raw('select 1 from member_banned where mid = member.mid')],
88 | ])->select();
89 | ```
90 |
91 |
92 |
93 | 通过上述的简介,相信你对Dce已经有了一个初步认识,Dce的玩法远不止这些,你可以[点击这里](https://drunkce.com/guide/)继续深入了解。
--------------------------------------------------------------------------------
/engine/db/connector/DbPool.php:
--------------------------------------------------------------------------------
1 | connect($config->dbName, $config->host, $config->dbUser, $config->dbPassword, $config->dbPort, false);
26 | return $connector;
27 | }
28 |
29 | public function fetch(): DbConnector {
30 | return $this->get();
31 | }
32 |
33 | /**
34 | * @param string ...$identities
35 | * @return static
36 | */
37 | public static function inst(string ... $identities): static {
38 | $inst = parent::getInstance(DbPoolProductionConfig::class, ... $identities);
39 | $inst->dbAlias ??= $identities[0];
40 | return $inst;
41 | }
42 |
43 | protected function retryable(Throwable $throwable): Throwable|bool {
44 | // 是否连接丢失的异常
45 | $result = $throwable instanceof PDOException && in_array($throwable->errorInfo[1] ?? 0, self::PdoDisconnectedCodes);
46 |
47 | // 若当前库开启了分库事务,且事务中成功发送过请求,则禁止重试连接
48 | if ($result && false !== Structure::arraySearch(function($shardingAlias) {
49 | $transaction = ShardingTransaction::aliasMatch($shardingAlias);
50 | $transaction && $transaction->clearBounds();
51 | return $transaction->uses ?? 0 > 1;
52 | }, array_unique(array_column(isset(Dce::$config->sharding)
53 | ? Dce::$config->sharding->filter(fn($c) => key_exists($this->dbAlias, $c->mapping)) : [], 'alias')))
54 | ) $result = new PoolException(PoolException::DISCONNECTED_TRANSACTION_ACTIVATED, previous: $throwable);
55 |
56 | return $result;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/engine/db/query/builder/schema/JoinSchema.php:
--------------------------------------------------------------------------------
1 | addTable($tableName, $alias);
18 | if ($on instanceof WhereSchema) {
19 | $whereSchema = $on;
20 | } else {
21 | if (! is_array($on)) {
22 | if (is_string($on)) {
23 | $on = [new RawBuilder($on)];
24 | } else if ($on instanceof RawBuilder) {
25 | $on = [$on];
26 | } else {
27 | $on = [[$on]];
28 | }
29 | }
30 | $whereSchema = new WhereSchema();
31 | $whereSchema->addCondition($on, false, false, 'AND');
32 | }
33 | $typeUpper = strtoupper(trim($type));
34 | if (! in_array($typeUpper, ['INNER', 'LEFT', 'RIGHT'])) {
35 | throw (new QueryException(QueryException::INNER_TYPE_INVALID))->format($typeUpper);
36 | }
37 | $this->mergeParams($tableSchema->getParams());
38 | $this->mergeParams($whereSchema->getParams());
39 | $this->pushCondition([
40 | 'tableSchema' => $tableSchema,
41 | 'whereSchema' => $whereSchema,
42 | 'joinType' => $typeUpper,
43 | ]);
44 | }
45 |
46 | public function __toString(): string {
47 | $joinConditionSqlPack = [];
48 | foreach ($this->getConditions() as $condition) {
49 | $tableSchema = $condition['tableSchema'];
50 | $whereSchema = $condition['whereSchema'];
51 | $joinType = $condition['joinType'];
52 | $joinConditionSqlPack[] = "{$joinType} JOIN {$tableSchema}" . ($whereSchema->isEmpty() ? '' : " ON {$whereSchema}");
53 | }
54 | return implode(' ', $joinConditionSqlPack);
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/engine/i18n/Locale.php:
--------------------------------------------------------------------------------
1 | lang = $this->parseLang($request);
25 | $this->country = $this->parseCountry($request);
26 | }
27 |
28 | /**
29 | * 根据请求解析当前语言码
30 | * @param Request|null $request
31 | * @return string
32 | */
33 | private function parseLang(Request|null $request): string {
34 | return isset(Dce::$config->app['lang_parse'])
35 | ? call_user_func(Dce::$config->app['lang_parse'], $request)
36 | : $request->request['lang'] ?? (isset($request->cookie)
37 | ? $request->cookie->get('lang') ?: Dce::$config->app['lang']
38 | : $request->rawRequest->cookie['lang'] ?? Dce::$config->app['lang']
39 | );
40 | }
41 |
42 | /**
43 | * 解析用户国家码, 可以配置国家吗解析器解析, 也可以在控制器中手动解析再赋值到request->locale上
44 | * @param Request|null $request
45 | * @return string
46 | */
47 | private function parseCountry(Request|null $request): string {
48 | return isset(Dce::$config->app['country_parse'])
49 | ? call_user_func(Dce::$config->app['country_parse'], $request)
50 | : Dce::$config->app['country'];
51 | }
52 |
53 | /**
54 | * 取当前请求客户端本地化参数
55 | * @return static
56 | */
57 | public static function client(): self {
58 | $request = RequestManager::current();
59 | return $request->locale ?? self::server();
60 | }
61 |
62 | /**
63 | * 取服务器本地化参数
64 | * @return static
65 | */
66 | public static function server(): self {
67 | static $instance;
68 | if (null === $instance) {
69 | $instance = new self;
70 | $instance->lang = Dce::$config->app['lang'];
71 | $instance->country = Dce::$config->app['country'];
72 | }
73 | return $instance;
74 | }
75 | }
--------------------------------------------------------------------------------
/engine/db/query/builder/statement/InsertStatement.php:
--------------------------------------------------------------------------------
1 | tableSchema = $tableSchema;
20 | $this->insertSchema = $insertSchema;
21 | $this->updateOrIgnore = $updateOrIgnore || is_array($updateOrIgnore) ?: $updateOrIgnore;
22 | $this->valid();
23 | $this->updateOrIgnore && $this->setConflictUpdate(is_array($updateOrIgnore) ? $updateOrIgnore : []);
24 | $this->mergeParams($this->insertSchema->getParams());
25 | $this->logHasSubQuery($this->insertSchema->hasSubQuery());
26 | }
27 |
28 | private function setConflictUpdate(array $excludeColumns): void {
29 | $this->updateColumns = array_filter($this->insertSchema->getColumns(), fn($c) => ! in_array($c, $excludeColumns), ARRAY_FILTER_USE_KEY);
30 | ! $this->updateColumns && throw new QueryException(QueryException::CONFLICT_UPDATE_COLUMNS_CANNOT_BE_EMPTY);
31 | }
32 |
33 | public function isBatch(): bool {
34 | return $this->insertSchema->isBatchInsert();
35 | }
36 |
37 | public function getUpdateOrIgnore(): bool|null {
38 | return $this->updateOrIgnore;
39 | }
40 |
41 | public function __toString(): string {
42 | $insertSql = $this->updateOrIgnore === false ? 'INSERT IGNORE' : 'INSERT';
43 | $updateSql = $this->updateOrIgnore ? ' AS EXCLUDED ON DUPLICATE KEY UPDATE ' . implode(', ', array_map(fn($c) => "$c=EXCLUDED.$c", $this->updateColumns)) : '';
44 | return "$insertSql INTO $this->tableSchema $this->insertSchema$updateSql";
45 | }
46 |
47 | protected function valid(): void {
48 | $this->tableSchema->isEmpty() && throw new QueryException(QueryException::INSERT_TABLE_NOT_SPECIFIED);
49 | $this->insertSchema->isEmpty() && throw new QueryException(QueryException::NO_INSERT_DATA);
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/engine/project/ProjectManager.php:
--------------------------------------------------------------------------------
1 | projectPaths ?? []
29 | ] as $path
30 | ) {
31 | ! is_dir($path) && throw (new ProjectException(ProjectException::PROJECT_PATH_INVALID))->format($path);
32 | $lastPathChar = substr($path, -1);
33 | if ($lastPathChar === '/') { // 以斜杠结尾的, 将扫描该路径下的目录作为项目
34 | // 应用目录下所有子目录均视为项目
35 | $projectPaths = [... $projectPaths, ... glob($path . '*', GLOB_ONLYDIR) ?: []];
36 | } else { // 非以斜杠结尾的, 则直接视为项目目录
37 | $projectPaths[] = $path;
38 | }
39 | }
40 | foreach ($projectPaths as $projectPath) {
41 | $projectName = pathinfo($projectPath, PATHINFO_FILENAME);
42 | $projects[$projectName] = new Project($projectName, realpath($projectPath) . '/');
43 | // 预加载项目类库
44 | Loader::prepareProject($projects[$projectName]);
45 | }
46 | self::$projects = $projects;
47 | }
48 |
49 | /**
50 | * 取指定项目
51 | * @param string $projectName
52 | * @return Project|null
53 | */
54 | public static function get(string $projectName): Project|null {
55 | return self::$projects[$projectName] ?? null;
56 | }
57 |
58 | /**
59 | * 取所有项目
60 | * @param bool|null $justSystematic
61 | * @return Project[]
62 | */
63 | public static function getAll(bool $justSystematic = null): array {
64 | return null === $justSystematic ? self::$projects : array_filter(self::$projects, fn($project) => $project->isSystematic === $justSystematic);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/engine/sharding/parser/mysql/MysqlValueParser.php:
--------------------------------------------------------------------------------
1 | value = $value;
29 | }
30 |
31 | public function toArray(): array {
32 | return [
33 | 'type' => 'value',
34 | 'value' => $this->value,
35 | ];
36 | }
37 |
38 | public function __toString(): string {
39 | $value = $this->isNumeric ? $this->value : "'{$this->value}'";
40 | return $value;
41 | }
42 |
43 | public static function build(string $statement, int|null & $offset, string $operator): self|null {
44 | $instance = new self($statement, $offset);
45 | if ($instance->detect($operator)) {
46 | // 解析引号字符串
47 | $string = $instance->parseString($operator);
48 | $instance->parse($string);
49 | } else if (self::isMinus($operator)) {
50 | // 解析负数
51 | $value = $instance->preParseWord();
52 | if (! is_numeric($value)) {
53 | throw new StatementParserException(StatementParserException::INVALID_NUMBER_AFTER_MINUS);
54 | }
55 | $instance->isNumeric = true;
56 | $instance->parse(- $value);
57 | } else {
58 | return null;
59 | }
60 | return $instance;
61 | }
62 |
63 | public static function buildByWord(string $statement, int|null & $offset, string $value): self|null {
64 | $instance = new self($statement, $offset);
65 | if ($instance->detectByWord($value)) {
66 | $instance->parse($value);
67 | $instance->isNumeric = true;
68 | return $instance;
69 | }
70 | return null;
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/engine/project/request/RequestManager.php:
--------------------------------------------------------------------------------
1 | [] 请求映射表 */
19 | private static array $requestMapping = [];
20 |
21 | /**
22 | * 实例化请求器并路由
23 | * @param class-string $rawRequestClass
24 | * @param mixed ...$rawRequestParams
25 | * @return Request
26 | * @throws RequestException
27 | * @throws Throwable
28 | */
29 | public static function route(string $rawRequestClass, mixed ... $rawRequestParams): Request {
30 | $rawRequest = new $rawRequestClass(... $rawRequestParams);
31 | $rawRequest->init();
32 | $request = new Request($rawRequest);
33 | self::logMapping($request);
34 | $request->route();
35 | return $request;
36 | }
37 |
38 | /** 尝试清理过期请求的映射记录 */
39 | private static function logMapping(Request $request): void {
40 | self::$requestMapping[$request->id] = WeakReference::create($request);
41 | // 每隔一定轮数清理一次过期请求的残留映射记录
42 | if (! (count(self::$requestMapping) % self::CLEAR_MOD)) {
43 | foreach (self::$requestMapping as $k => $requestRef)
44 | if (! $requestRef->get()) unset(self::$requestMapping[$k]);
45 | }
46 | }
47 |
48 | /**
49 | * 取当前请求ID
50 | * - CGI模式时, 一个进程处理一个请求, 控制器间不会干扰, 无需为请求分配唯一ID, 返回0
51 | * - 非携程模式时, 请求间不会并行执行控制器, 也不会互相干扰, 无需唯一ID, 返回0或-1
52 | * - Swoole协程模式时, 请求控制器会并行执行, 可能会相互干扰产生污染, 但他们拥有不同的根协程ID, 可以以此作为其ID隔离出安全沙盒
53 | * @return int
54 | */
55 | public static function currentId(): int {
56 | $requestId = 0;
57 | if (SwooleUtility::inSwoole() && ($requestId = Coroutine::getCid()) > 0) {
58 | while (($pcid = Coroutine::getPcid($requestId)) > 0)
59 | $requestId = $pcid;
60 | }
61 | return $requestId;
62 | }
63 |
64 | /**
65 | * 取当前的请求对象
66 | * @return Request|null
67 | */
68 | public static function current(): Request|null {
69 | return ($requestRef = self::$requestMapping[self::currentId()] ?? null) ? $requestRef->get() : null;
70 | }
71 | }
--------------------------------------------------------------------------------
/project/tcp/service/RawRequestTcp.php:
--------------------------------------------------------------------------------
1 | isConnecting = true;
30 | $this->connection = $data;
31 | $data = null;
32 | } else {
33 | $this->connection = Connection::from($fd);
34 | }
35 | $this->raw = [
36 | 'fd' => $fd,
37 | 'reactor_id' => $reactorId,
38 | 'data' => $data,
39 | ];
40 | }
41 |
42 | /** @inheritDoc */
43 | public function getServer(): ServerMatrix {
44 | return $this->server;
45 | }
46 |
47 | /** @inheritDoc */
48 | public function getRaw(): array {
49 | return $this->raw;
50 | }
51 |
52 | /** @inheritDoc */
53 | public function init(): void {
54 | ['path' => $this->path, 'requestId' => $this->requestId, 'data' => $this->rawData, 'dataParsed' => $this->dataParsed] = $this->isConnecting
55 | ? ['path' => $this->connection->initialNode->pathFormat, 'requestId' => null, 'data' => '', 'dataParsed' => null] : $this->unPack($this->raw['data']);
56 | }
57 |
58 | /** @inheritDoc */
59 | public function supplementRequest(Request $request): void {
60 | $request->fd = $this->fd;
61 | $request->rawData = $this->rawData;
62 | $request->request = is_array($this->dataParsed) ? $this->dataParsed : [];
63 | $request->session = $this->connection->session;
64 | }
65 |
66 | /** @inheritDoc */
67 | public function response(mixed $data, string|false $path): bool {
68 | LogManager::response($this, $data);
69 | return $this->server->send($this->raw['fd'], $data, $path . (isset($this->requestId) ? self::REQUEST_SEPARATOR . $this->requestId : ''));
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/engine/project/request/Pageable.php:
--------------------------------------------------------------------------------
1 | */
20 | public array $list = [];
21 |
22 | public function __construct(
23 | public array $filters = [],
24 | int $page = 0,
25 | int $pageSize = 0,
26 | ){
27 | $pageSize < 1 && $pageSize = $filters['pageSize'] ?? 0;
28 | $pageSize < 1 && $pageSize = 20;
29 | $this->pageSize = $pageSize;
30 | $page < 1 && $page = $filters['page'] ?? 0;
31 | $page < 1 ? $this->setOffset($filters['offset'] ?? 0) : $this->setPage($page);
32 | }
33 |
34 | public function setPage(int $page): void {
35 | $this->page = $page;
36 | $this->offset = ($page - 1) * $this->pageSize;
37 | }
38 |
39 | public function setOffset(int $offset): void {
40 | $this->page = floor($offset / $this->pageSize) + 1;
41 | $this->offset = $offset;
42 | }
43 |
44 | public function setTotal(int $total): void {
45 | $this->total = $total;
46 | $this->totalPage = ceil($total / $this->pageSize);
47 | }
48 |
49 | /** @param array $list */
50 | public function setList(array $list): void {
51 | $this->list = $list;
52 | }
53 |
54 | public function setExt(string $key, mixed $value): void {
55 | $this->extAttrs[$key] = $value;
56 | }
57 |
58 | public function setAttr(string $key, mixed $value): void {
59 | $this->rootAttrs[$key] = $value;
60 | }
61 |
62 | public function getAttr(string $key): mixed {
63 | return $this->rootAttrs[$key];
64 | }
65 |
66 | public function hasAttr(string $key): bool {
67 | return key_exists($key, $this->rootAttrs);
68 | }
69 |
70 | public function build(array $list = []): array {
71 | $resp = $this->rootAttrs;
72 | isset($this->page) && $resp['page'] = $this->page;
73 | isset($this->total) && $resp['total'] = $this->total;
74 | $resp['list'] = $list ?: array_map(fn($ar) => $ar->extract(), $this->list);
75 | return $resp;
76 | }
77 | }
--------------------------------------------------------------------------------
/engine/pool/PoolProduct.php:
--------------------------------------------------------------------------------
1 | mapping[$id])) {
40 | throw new PoolException(PoolException::INVALID_CHANNEL_INSTANCE);
41 | }
42 | return $this->mapping[$id];
43 | }
44 |
45 | /**
46 | * 映射标记
47 | * @param object $object
48 | * @param PoolProductionConfig $config
49 | * @param ChannelAbstract $channel
50 | */
51 | public function set(object $object, PoolProductionConfig $config, ChannelAbstract $channel): void {
52 | $id = spl_object_id($object);
53 | if (! key_exists($id, $this->mapping)) {
54 | $this->mapping[$id] = self::new()->setProperties([
55 | 'product_ref' => WeakReference::create($object),
56 | 'config' => $config,
57 | 'channel' => $channel,
58 | ]);
59 | }
60 | }
61 |
62 | /**
63 | * 更新实例最后取出时间
64 | * @param object $object
65 | * @throws PoolException
66 | */
67 | public function refresh(object $object): void {
68 | $this->get($object)->lastFetch = time();
69 | }
70 |
71 | /**
72 | * 清除失效没回收的实例
73 | * @param PoolProductionConfig $config
74 | */
75 | public function clear(PoolProductionConfig $config): void {
76 | foreach ($this->mapping as $id => $item) {
77 | if ($config === $item->config && ! $item->productRef->get()) {
78 | // 销毁实例减产量
79 | $item->config->destroy();
80 | // 销毁实例退销量, (因为只有在生产消费率与生产率满时才进到此, 所以必定是有消费了未退还的实例, 所以此处可以安全退还)
81 | $item->config->return();
82 | // 销毁实例相关映射
83 | unset($this->mapping[$id]);
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/engine/sharding/id_generator/server/IdgServerProducer.php:
--------------------------------------------------------------------------------
1 | batchCount)) {
21 | // 如果未配置单次批量ID数, 则默认为8192
22 | $batch->batchCount = self::$default_batch_count;
23 | }
24 | // 如果未初始化过, 则初始化新批起始值为1 (自增型ID从1开始递增), 否则为前次截至值+1
25 | $batch->batchFrom = ($batch->batchTo ?? 0) + 1;
26 | $batch->batchTo = $batch->batchFrom + $batch->batchCount - 1;
27 | }
28 |
29 | /**
30 | * TimeId池生成器
31 | * @param IdgBatch $batch
32 | * @throws IdgException
33 | */
34 | protected function produceTime(IdgBatch $batch): void {
35 | $time = time();
36 | if (! isset($batch->timeId)) {
37 | $batch->timeId = $time;
38 | }
39 | if (! isset($batch->batchBitWidth)) {
40 | throw new IdgException(IdgException::BATCH_BIT_WIDTH_MISSING);
41 | }
42 | if (! isset($batch->batchCount)) {
43 | $batch->batchCount = self::$default_batch_count;
44 | }
45 | $maxBatchId = (1 << $batch->batchBitWidth) - 1; // 计算最大批次ID
46 | if (! isset($batch->batchTo)) { // 如果未初始化过, 则初始化新批起始值为0
47 | $batch->batchFrom = 0;
48 | } else if ($time > $batch->timeId) { // 如果距前次生产已过1秒
49 | $batch->timeId = $time; // 记录新的时间戳
50 | $batch->batchFrom = 0; // 初始化新批起始值为0
51 | } else {
52 | $batch->batchFrom += $batch->batchCount; // 初始化新批起始值为0
53 | if ($batch->batchFrom > $maxBatchId) { // 如果ID起始值超出范围, 则表示同一秒内生产了过多的ID
54 | $microTime = microtime(1);
55 | $nextLess = 1000000 * (ceil($microTime) - $microTime) + 777; // 算出距下一秒的剩余微妙数
56 | usleep($nextLess); // 阻塞到下一秒继续生产 (您也可以将单秒ID池调大)
57 | $this->produceTime($batch);
58 | return;
59 | }
60 | }
61 | $batch->batchTo = $batch->batchFrom + $batch->batchCount - 1;
62 | if ($batch->batchTo > $maxBatchId) { // 如果截止ID超出范围, 则重定义为最大有效ID
63 | $batch->batchTo = $maxBatchId;
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------