仓库金融单据服务
Some checks failed
Build Docker / build (push) Has been cancelled

This commit is contained in:
2025-07-08 15:10:36 +08:00
commit 7423491d9c
69 changed files with 12995 additions and 0 deletions

View File

@ -0,0 +1,24 @@
<?php
declare(strict_types=1);
namespace App\Amqp\Consumer;
use App\Service\AliLogsSignService;
use Hyperf\Amqp\Result;
use Hyperf\Amqp\Annotation\Consumer;
use Hyperf\Di\Annotation\Inject;
#[Consumer(exchange: 'wh_service_ali_sls', routingKey: 'wh_service_ali_sls_key', queue: 'wh_service_ali_sls_queue', name: "AliSlsConsumer", nums: 5)]
class AliSlsConsumer extends BaseConsumer
{
#[Inject]
protected AliLogsSignService $aliLogsSignService;
public function handle($data): Result
{
$this->aliLogsSignService->putWebTracking($data);
return Result::ACK;
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:37
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Amqp\Consumer;
use Hyperf\Amqp\Message\ConsumerMessage;
use Hyperf\Amqp\Result;
use PhpAmqpLib\Exception\AMQPChannelClosedException;
use PhpAmqpLib\Message\AMQPMessage;
use App\Log\Log;
use Exception;
use PhpAmqpLib\Exception\AMQPConnectionClosedException;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:18
* Description: 消费者抽象基类.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
abstract class BaseConsumer extends ConsumerMessage
{
/**
* 消费者处理逻辑
* @param mixed $data
* @param AMQPMessage $message
* @return Result
* @throws Exception
*/
public function consumeMessage($data, AMQPMessage $message): Result
{
$consumerClass = get_class($this);
try {
return $this->handle($data);
} catch (AMQPChannelClosedException | AMQPConnectionClosedException $e) {
Log::get('queue', 'queue')->error("AMQP通道关闭异常 ($consumerClass): " . $e->getMessage(), [
'data' => $data,
'exception' => $e,
]);
// 可选:重连逻辑 or 丢弃
return Result::ACK; // 或 NACK
} catch (Exception $e) {
Log::get('queue', 'queue')->error("AMQP消费者异常 ($consumerClass): " . $e->getMessage(), [
'data' => $data,
'exception' => $e,
]);
return Result::ACK;
}
}
/**
* 子类实现的核心处理逻辑
* @param mixed $data
* @return Result
*/
abstract protected function handle(mixed $data): Result;
}

View File

@ -0,0 +1,18 @@
<?php
declare(strict_types=1);
namespace App\Amqp\Producer;
use Hyperf\Amqp\Annotation\Producer;
use Hyperf\Amqp\Message\ProducerMessage;
#[Producer(exchange: 'wh_service_ali_sls', routingKey: 'wh_service_ali_sls_key')]
class AliSlsProducer extends ProducerMessage
{
public function __construct($data)
{
$this->payload = $data;
$this->properties['delivery_mode'] = 2; // 消息持久化
}
}

View File

@ -0,0 +1,25 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Constants;
use Hyperf\Constants\AbstractConstants;
use Hyperf\Constants\Annotation\Constants;
#[Constants]
class ErrorCode extends AbstractConstants
{
/**
* @Message("Server Error")
*/
public const SERVER_ERROR = 500;
}

View File

@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Controller;
use Hyperf\Di\Annotation\Inject;
use Hyperf\HttpServer\Contract\RequestInterface;
use Hyperf\HttpServer\Contract\ResponseInterface;
use Psr\Container\ContainerInterface;
abstract class AbstractController
{
#[Inject]
protected ContainerInterface $container;
#[Inject]
protected RequestInterface $request;
#[Inject]
protected ResponseInterface $response;
}

View File

@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Controller;
class IndexController extends AbstractController
{
public function index()
{
$user = $this->request->input('user', 'Hyperf');
$method = $this->request->getMethod();
return [
'method' => $method,
'message' => "Hello {$user}.",
];
}
}

View File

@ -0,0 +1,29 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Exception;
use App\Constants\ErrorCode;
use Hyperf\Server\Exception\ServerException;
use Throwable;
class BusinessException extends ServerException
{
public function __construct(int $code = 0, string $message = null, Throwable $previous = null)
{
if (is_null($message)) {
$message = ErrorCode::getMessage($code);
}
parent::__construct($message, $code, $previous);
}
}

View File

@ -0,0 +1,38 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Exception\Handler;
use Hyperf\Contract\StdoutLoggerInterface;
use Hyperf\ExceptionHandler\ExceptionHandler;
use Hyperf\HttpMessage\Stream\SwooleStream;
use Psr\Http\Message\ResponseInterface;
use Throwable;
class AppExceptionHandler extends ExceptionHandler
{
public function __construct(protected StdoutLoggerInterface $logger)
{
}
public function handle(Throwable $throwable, ResponseInterface $response)
{
$this->logger->error(sprintf('%s[%s] in %s', $throwable->getMessage(), $throwable->getLine(), $throwable->getFile()));
$this->logger->error($throwable->getTraceAsString());
return $response->withHeader('Server', 'Hyperf')->withStatus(500)->withBody(new SwooleStream('Internal Server Error.'));
}
public function isValid(Throwable $throwable): bool
{
return true;
}
}

View File

@ -0,0 +1,36 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/5
* * Time: 下午1:56
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\JsonRpc;
use App\Service\JsonRpcResponse;
use Hyperf\Di\Annotation\Inject;
/**
* Author: ykxiao
* Date: 2025/6/5
* Time: 下午1:56
* Description: 服务基类.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
abstract class BaseService
{
#[Inject]
protected JsonRpcResponse $response;
}

View File

@ -0,0 +1,27 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/5
* Time: 下午1:45
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\JsonRpc;
use Hyperf\RpcServer\Annotation\RpcService;
#[RpcService(name: 'InventoryService', server: 'jsonrpc-http', protocol: 'jsonrpc-http', publishTo: 'nacos')]
class InventoryService extends BaseService implements InventoryServiceInterface
{
public function addInventory(array $data): void
{
}
}

View File

@ -0,0 +1,26 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/5
* Time: 下午1:46
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\JsonRpc;
interface InventoryServiceInterface
{
/**
* 新增库存-手动单条.
* @param array $data
* @return void
*/
public function addInventory(array $data): void;
}

View File

@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Listener;
use Hyperf\Collection\Arr;
use Hyperf\Database\Events\QueryExecuted;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
#[Listener]
class DbQueryExecutedListener implements ListenerInterface
{
/**
* @var LoggerInterface
*/
private $logger;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerFactory::class)->get('sql');
}
public function listen(): array
{
return [
QueryExecuted::class,
];
}
/**
* @param QueryExecuted $event
*/
public function process(object $event): void
{
if ($event instanceof QueryExecuted) {
$sql = $event->sql;
if (! Arr::isAssoc($event->bindings)) {
$position = 0;
foreach ($event->bindings as $value) {
$position = strpos($sql, '?', $position);
if ($position === false) {
break;
}
$value = "'{$value}'";
$sql = substr_replace($sql, $value, $position, 1);
$position += strlen($value);
}
}
$this->logger->info(sprintf('[%s] %s', $event->time, $sql));
}
}
}

View File

@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Listener;
use Hyperf\AsyncQueue\AnnotationJob;
use Hyperf\AsyncQueue\Event\AfterHandle;
use Hyperf\AsyncQueue\Event\BeforeHandle;
use Hyperf\AsyncQueue\Event\Event;
use Hyperf\AsyncQueue\Event\FailedHandle;
use Hyperf\AsyncQueue\Event\RetryHandle;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
#[Listener]
class QueueHandleListener implements ListenerInterface
{
protected LoggerInterface $logger;
public function __construct(ContainerInterface $container)
{
$this->logger = $container->get(LoggerFactory::class)->get('queue');
}
public function listen(): array
{
return [
AfterHandle::class,
BeforeHandle::class,
FailedHandle::class,
RetryHandle::class,
];
}
public function process(object $event): void
{
if ($event instanceof Event && $event->getMessage()->job()) {
$job = $event->getMessage()->job();
$jobClass = get_class($job);
if ($job instanceof AnnotationJob) {
$jobClass = sprintf('Job[%s@%s]', $job->class, $job->method);
}
$date = date('Y-m-d H:i:s');
switch (true) {
case $event instanceof BeforeHandle:
$this->logger->info(sprintf('[%s] Processing %s.', $date, $jobClass));
break;
case $event instanceof AfterHandle:
$this->logger->info(sprintf('[%s] Processed %s.', $date, $jobClass));
break;
case $event instanceof FailedHandle:
$this->logger->error(sprintf('[%s] Failed %s.', $date, $jobClass));
$this->logger->error((string) $event->getThrowable());
break;
case $event instanceof RetryHandle:
$this->logger->warning(sprintf('[%s] Retried %s.', $date, $jobClass));
break;
}
}
}
}

View File

@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Listener;
use Hyperf\Command\Event\AfterExecute;
use Hyperf\Coordinator\Constants;
use Hyperf\Coordinator\CoordinatorManager;
use Hyperf\Event\Annotation\Listener;
use Hyperf\Event\Contract\ListenerInterface;
#[Listener]
class ResumeExitCoordinatorListener implements ListenerInterface
{
public function listen(): array
{
return [
AfterExecute::class,
];
}
public function process(object $event): void
{
CoordinatorManager::until(Constants::WORKER_EXIT)->resume();
}
}

46
app/Log/AliSlsHandler.php Normal file
View File

@ -0,0 +1,46 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:33
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use App\Amqp\Producer\AliSlsProducer;
use Hyperf\Amqp\Producer;
use Hyperf\Di\Annotation\Inject;
use Monolog\Handler\AbstractProcessingHandler;
use Monolog\LogRecord;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:19
* Description: 阿里云日志服务处理器.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class AliSlsHandler extends AbstractProcessingHandler
{
#[Inject]
protected Producer $producer;
protected function write(LogRecord $record): void
{
$logs = ['channel' => $record['channel'] ?? '', 'formatted' => $record['formatted'] ?? ''];
$message = new AliSlsProducer($logs);
$this->producer->produce($message, true);
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:33
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use Hyperf\Context\Context;
use Hyperf\Coroutine\Coroutine;
use Monolog\LogRecord;
use Monolog\Processor\ProcessorInterface;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:19
* Description: 添加请求ID和协程ID
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class AppendRequestIdProcessor implements ProcessorInterface
{
public const string REQUEST_ID = 'log.request.id';
public function __invoke(LogRecord $record): array|LogRecord
{
$record['extra']['request_id'] = Context::getOrSet(self::REQUEST_ID, uniqid('xw_cloud'));
$record['extra']['coroutine_id'] = Coroutine::id();
return $record;
}
}

63
app/Log/Log.php Normal file
View File

@ -0,0 +1,63 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:42
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use Exception;
use Hyperf\Context\ApplicationContext;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
use function Hyperf\Support\make;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:19
* Description: 日志记录器类。
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class Log
{
#[Inject]
protected LoggerInterface $logger;
/**
* 根据提供的名称和分组获取日志记录器实例。
*
* @param string $name 日志记录器的名称,默认为'app'。
* @param string $group 日志记录器的分组,默认为'job'。
* @return LoggerInterface 返回一个日志记录器实例。
* @throws Exception
*/
public static function get(string $name = 'app', string $group = 'job'): LoggerInterface
{
try {
// 尝试从应用上下文容器中获取LoggerFactory实例并进一步获取指定名称和分组的日志记录器
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get($name, $group);
} catch (ContainerExceptionInterface|NotFoundExceptionInterface $e) {
// 如果在获取过程中发生异常使用默认的日志记录器记录错误信息并抛出ApiException
$logs = make(LoggerFactory::class)->get('default');
$logs->error($e->getMessage());
throw new Exception($e->getMessage());
}
}
}

View File

@ -0,0 +1,48 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:03
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Log;
use Hyperf\Context\ApplicationContext;
use Hyperf\Logger\LoggerFactory;
use Psr\Container\ContainerExceptionInterface;
use Psr\Container\ContainerInterface;
use Psr\Container\NotFoundExceptionInterface;
use Psr\Log\LoggerInterface;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午6:04
* Description: 创建一个日志记录器
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class StdoutLoggerFactory
{
/**
* @param ContainerInterface $container
* @return LoggerInterface
* @throws ContainerExceptionInterface
* @throws NotFoundExceptionInterface
*/
public function __invoke(ContainerInterface $container): LoggerInterface
{
return ApplicationContext::getContainer()->get(LoggerFactory::class)->get();
}
}

22
app/Model/Model.php Normal file
View File

@ -0,0 +1,22 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Model;
use Hyperf\DbConnection\Model\Model as BaseModel;
use Hyperf\ModelCache\Cacheable;
use Hyperf\ModelCache\CacheableInterface;
abstract class Model extends BaseModel implements CacheableInterface
{
use Cacheable;
}

View File

@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact group@hyperf.io
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/
namespace App\Process;
use Hyperf\AsyncQueue\Process\ConsumerProcess;
use Hyperf\Process\Annotation\Process;
#[Process]
class AsyncQueueConsumer extends ConsumerProcess
{
}

View File

@ -0,0 +1,158 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:13
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Service;
use Hyperf\Config\Annotation\Value;
use Hyperf\Di\Annotation\Inject;
use Hyperf\Guzzle\ClientFactory;
use function Hyperf\Coroutine\co;
/**
* Author: ykxiao
* Date: 2025/6/3
* Time: 下午7:18
* Description: 阿里云日志服务.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class AliLogsSignService
{
protected const string LOG_SIGNATURE_METHOD = 'hmac-sha1';
protected const string LOG_API_VERSION = '0.6.0';
#[Value('alibaba.accessKeyId')]
protected string $accessKeyId;
#[Value('alibaba.accessKeySecret')]
protected string $accessKeySecret;
#[Value('alibaba.logs')]
protected array $logs;
#[Inject]
protected ClientFactory $clientFactory;
/**
* 签名及请求头拼接.
* @param mixed $method
* @param mixed $uri
* @param mixed $params
* @param mixed $body
* @param mixed $logProject
* @param mixed $logEndpoint
*/
public function buildHeaders(
string $method,
string $uri,
array $params,
string $body,
string|object $logProject,
string $logEndpoint
): array
{
$headers = [
'x-log-signaturemethod' => self::LOG_SIGNATURE_METHOD,
'x-log-apiversion' => self::LOG_API_VERSION,
'Host' => sprintf('%s.%s', $logProject, $logEndpoint),
'Content-Type' => 'application/json',
];
$contentLength = 0;
$contentMd5 = '';
if (!empty($body) && strlen($body) > 0) {
$contentLength = strlen($body);
$contentMd5 = strtoupper(md5($body));
$headers['Content-MD5'] = $contentMd5;
}
// date
setlocale(LC_TIME, 'en_US');
$date = gmdate('D, d M Y H:i:s \G\M\T', time());
$headers['Date'] = $date;
$headers['Content-Length'] = (string)$contentLength;
$contentType = $headers['Content-Type'];
$message = $method . "\n" . $contentMd5 . "\n" . $contentType . "\n" . $date . "\n";
// header
$filterHeaders = [];
foreach ($headers as $key => $val) {
if (str_starts_with($key, 'x-log-') || str_starts_with($key, 'x-acs-')) {
$filterHeaders[$key] = $val;
}
}
ksort($filterHeaders);
foreach ($filterHeaders as $key => $val) {
$message .= $key . ':' . $val . "\n";
}
// uri and params
$message .= $uri;
if (sizeof($params) > 0) {
$message .= '?';
}
ksort($params);
$sep = '';
foreach ($params as $key => $val) {
$message .= $sep . $key . '=' . $val;
$sep = '&';
}
// signature & authorization
$signature = $this->generateSignature($message);
$auth = 'LOG ' . $this->accessKeyId . ':' . $signature;
$headers['Authorization'] = $auth;
return $headers;
}
/**
* 实现调用PutWebTracking接口将多条日志合并进行采集.
*/
public function putWebTracking(array $record): void
{
$logEndpoint = $this->logs['log_endpoint'];
$logProject = $this->logs['log_project'];
$logStores = $this->logs['log_store'];
$params = [];
$body = [
'__topic__' => 'mes api logs',
'__source__' => 'admin api',
'__logs__' => [
['Logs' => $record['formatted']],
],
'__tags__' => [
'mes' => $record['channel'],
],
];
$body = json_encode($body);
$sign_url = sprintf('/logstores/%s/track', $logStores);
$headers = $this->buildHeaders('POST', $sign_url, $params, $body, $logProject, $logEndpoint);
$options = [
'headers' => $headers,
'body' => $body,
'query' => $params,
];
$url = sprintf('https://%s.%s/logstores/%s/track', $logProject, $logEndpoint, $logStores);
$client = $this->clientFactory->create();
co(function () use ($client, $url, $options) {
$client->request('POST', $url, $options);
});
}
protected function generateSignature(string $message): string
{
return base64_encode(hash_hmac('sha1', $message, $this->accessKeySecret, true));
}
}

View File

@ -0,0 +1,97 @@
<?php
/**
* Author: ykxiao
* Date: 2024/6/3
* Time: 22:45
* Description:
*/
declare(strict_types=1);
namespace App\Service;
use Hyperf\Logger\LoggerFactory;
use Psr\Log\LoggerInterface;
/**
* Author: ykxiao
* Date: 2025/1/3
* Time: 下午8:33
* Description: JsonRpc响应类.
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
class JsonRpcResponse
{
/**
* JsonRpc版本。
*/
private string $jsonRpcVersion = '2.0';
private mixed $id;
private mixed $result;
private mixed $error;
protected LoggerInterface $logger;
public function __construct(LoggerFactory $loggerFactory, $id = null, $result = null, $error = null)
{
$this->id = $id;
$this->result = $result;
$this->error = $error;
$this->logger = $loggerFactory->get('jsonrpc');
}
public function withId($id): static
{
$this->id = $id;
return $this;
}
public function withResult($result): static
{
$this->result = $result;
$this->logger->info(json_encode(['RPC result' => $result], JSON_UNESCAPED_UNICODE));
return $this;
}
public function withError($code, $message, $data = null): static
{
$this->error = [
'code' => $code,
'message' => $message,
'data' => $data,
];
$this->logger->error(json_encode(['RPC error' => $message], JSON_UNESCAPED_UNICODE));
return $this;
}
public function toArray(): array
{
$response = [
'jsonrpc' => $this->jsonRpcVersion,
'id' => $this->id,
];
if ($this->error) {
$response['error'] = $this->error;
} else {
$response['result'] = $this->result;
}
return $response;
}
public function toJson(): bool|string
{
return json_encode($this->toArray());
}
}

View File

@ -0,0 +1,44 @@
<?php
/**
* Author: ykxiao
* Date: 2025/6/4
* Time: 下午1:57
* Description:
*
* (c) ykxiao <yk_9001@hotmail.com>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/
declare(strict_types=1);
namespace App\Service;
use Hyperf\Database\Schema\Blueprint;
class MigrateService
{
/***
* 在创建的时候自动添加如下字段
* @param Blueprint $blueprint
* @return Blueprint
*/
public static function migrateCreateInfo(Blueprint $blueprint): Blueprint
{
$blueprint->integer('creator_id')->default(0)->unsigned()->comment('创建人ID');
$blueprint->string('creator_name', 45)->default('')->comment('创建人姓名');
$blueprint->integer('created_at')->default(0)->unsigned()->comment('创建时间');
$blueprint->integer('updated_at')->default(0)->unsigned()->comment('更新时间');
$blueprint->integer('deleted_at')->nullable()->unsigned()->comment('软删除时间');
return $blueprint;
}
public static function migrateTime(Blueprint $blueprint): Blueprint
{
$blueprint->integer('created_at')->default(0)->unsigned()->comment('创建时间');
$blueprint->integer('updated_at')->default(0)->unsigned()->comment('更新时间');
$blueprint->integer('deleted_at')->nullable()->unsigned()->comment('软删除时间');
return $blueprint;
}
}