This commit is contained in:
24
app/Amqp/Consumer/AliSlsConsumer.php
Normal file
24
app/Amqp/Consumer/AliSlsConsumer.php
Normal 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;
|
||||
}
|
||||
}
|
74
app/Amqp/Consumer/BaseConsumer.php
Normal file
74
app/Amqp/Consumer/BaseConsumer.php
Normal 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;
|
||||
}
|
18
app/Amqp/Producer/AliSlsProducer.php
Normal file
18
app/Amqp/Producer/AliSlsProducer.php
Normal 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; // 消息持久化
|
||||
}
|
||||
}
|
25
app/Constants/ErrorCode.php
Normal file
25
app/Constants/ErrorCode.php
Normal 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;
|
||||
}
|
30
app/Controller/AbstractController.php
Normal file
30
app/Controller/AbstractController.php
Normal 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;
|
||||
}
|
27
app/Controller/IndexController.php
Normal file
27
app/Controller/IndexController.php
Normal 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}.",
|
||||
];
|
||||
}
|
||||
}
|
29
app/Exception/BusinessException.php
Normal file
29
app/Exception/BusinessException.php
Normal 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);
|
||||
}
|
||||
}
|
38
app/Exception/Handler/AppExceptionHandler.php
Normal file
38
app/Exception/Handler/AppExceptionHandler.php
Normal 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;
|
||||
}
|
||||
}
|
36
app/JsonRpc/BaseService.php
Normal file
36
app/JsonRpc/BaseService.php
Normal 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;
|
||||
}
|
27
app/JsonRpc/InventoryService.php
Normal file
27
app/JsonRpc/InventoryService.php
Normal 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
|
||||
{
|
||||
|
||||
}
|
||||
}
|
26
app/JsonRpc/InventoryServiceInterface.php
Normal file
26
app/JsonRpc/InventoryServiceInterface.php
Normal 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;
|
||||
}
|
66
app/Listener/DbQueryExecutedListener.php
Normal file
66
app/Listener/DbQueryExecutedListener.php
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
74
app/Listener/QueueHandleListener.php
Normal file
74
app/Listener/QueueHandleListener.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
35
app/Listener/ResumeExitCoordinatorListener.php
Normal file
35
app/Listener/ResumeExitCoordinatorListener.php
Normal 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
46
app/Log/AliSlsHandler.php
Normal 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);
|
||||
}
|
||||
}
|
44
app/Log/AppendRequestIdProcessor.php
Normal file
44
app/Log/AppendRequestIdProcessor.php
Normal 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
63
app/Log/Log.php
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
48
app/Log/StdoutLoggerFactory.php
Normal file
48
app/Log/StdoutLoggerFactory.php
Normal 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
22
app/Model/Model.php
Normal 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;
|
||||
}
|
21
app/Process/AsyncQueueConsumer.php
Normal file
21
app/Process/AsyncQueueConsumer.php
Normal 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
|
||||
{
|
||||
}
|
158
app/Service/AliLogsSignService.php
Normal file
158
app/Service/AliLogsSignService.php
Normal 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));
|
||||
}
|
||||
}
|
97
app/Service/JsonRpcResponse.php
Normal file
97
app/Service/JsonRpcResponse.php
Normal 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());
|
||||
}
|
||||
}
|
44
app/Service/MigrateService.php
Normal file
44
app/Service/MigrateService.php
Normal 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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user