This commit is contained in:
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));
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
121
app/Service/OpLogsService.php
Executable file
121
app/Service/OpLogsService.php
Executable file
@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/25
|
||||
* Time: 上午9:15
|
||||
* 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);
|
||||
/**
|
||||
* 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\Service;
|
||||
|
||||
use App\Context\QueueContext;
|
||||
use App\Context\UserContext;
|
||||
use Hyperf\Collection\Arr;
|
||||
use Hyperf\HttpServer\Contract\RequestInterface;
|
||||
use Hyperf\HttpServer\Router\Dispatched;
|
||||
|
||||
use function Hyperf\Support\make;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/25
|
||||
* Time: 上午9: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.
|
||||
*/
|
||||
class OpLogsService
|
||||
{
|
||||
/**
|
||||
* 记录操作日志。
|
||||
*
|
||||
* 该方法用于记录与请求相关联的操作日志,包括操作动作、路由、参数、IP地址、用户代理等信息。
|
||||
* 允许通过$logStream参数传递额外的备注信息,并可选择指定日志的来源。
|
||||
*
|
||||
* @param string $logStream 日志流或额外的备注信息,默认为空字符串
|
||||
* @param null|int $source 日志来源的标识,默认为0
|
||||
*/
|
||||
public function operatorLogs(string $logStream = '', ?int $source = 0): void
|
||||
{
|
||||
// 创建请求对象
|
||||
$request = make(RequestInterface::class);
|
||||
// 尝试从请求中获取调度信息
|
||||
$dispatched = $request->getAttribute(Dispatched::class);
|
||||
|
||||
// 如果不存在调度信息,则不记录日志
|
||||
if (! $dispatched instanceof Dispatched) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析路由处理器信息
|
||||
$handler = $dispatched->handler->callback ?? null;
|
||||
[$routeControllerName, $routeActionName] = $handler;
|
||||
// 提取控制器名称
|
||||
$controllerName = Arr::last(explode('\\', $routeControllerName));
|
||||
// 组装当前执行的动作字符串
|
||||
$currAction = $controllerName . '@' . $routeActionName;
|
||||
|
||||
// 过滤敏感参数后收集请求信息
|
||||
$params = $this->filterSensitiveParams($request->all());
|
||||
$clientIP = $this->getClientIP($request);
|
||||
$agent = $request->getHeaderLine('User-Agent');
|
||||
|
||||
// 准备日志数据
|
||||
$data = [
|
||||
'action' => $currAction,
|
||||
'route' => $request->path(),
|
||||
'params' => $params,
|
||||
'ip' => $clientIP,
|
||||
'agent' => $agent,
|
||||
'remark' => $logStream,
|
||||
'source' => $source,
|
||||
];
|
||||
// 将日志数据加入队列,以便后续处理
|
||||
make(QueueService::class)->make($data)->opLogs();
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤敏感参数.
|
||||
*/
|
||||
protected function filterSensitiveParams(array $params): array
|
||||
{
|
||||
$sensitiveKeys = ['password', 'pwd', 'pwd_conf', 'original_pwd', 'old_pwd', 'new_pwd', 'conf_pwd'];
|
||||
|
||||
foreach ($sensitiveKeys as $key) {
|
||||
Arr::forget($params, $key);
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取客户端 IP.
|
||||
*/
|
||||
protected function getClientIP(RequestInterface $request): string
|
||||
{
|
||||
$realIP = $request->getHeaderLine('x-real-ip');
|
||||
$forwardedFor = $request->getHeaderLine('x-forwarded-for');
|
||||
|
||||
$clientIP = $realIP ?: $forwardedFor;
|
||||
return $clientIP ?: '127.0.0.1';
|
||||
}
|
||||
}
|
92
app/Service/QueueService.php
Normal file
92
app/Service/QueueService.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/3
|
||||
* Time: 下午10:53
|
||||
* 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 App\Context\UserContext;
|
||||
use App\Job\ColumnConfigJob;
|
||||
use App\Job\OpLogsJob;
|
||||
use App\Job\RequestWriteLogsJob;
|
||||
use Hyperf\Amqp\Producer;
|
||||
use Hyperf\AsyncQueue\Driver\DriverFactory;
|
||||
use Hyperf\AsyncQueue\Driver\DriverInterface;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
|
||||
class QueueService
|
||||
{
|
||||
// 存储配置参数
|
||||
protected array $params = [];
|
||||
|
||||
// 当前操作的函数名
|
||||
protected string $function = '';
|
||||
|
||||
// 队列驱动实例
|
||||
protected DriverInterface $driver;
|
||||
|
||||
// 生产者实例,用于发送消息
|
||||
#[Inject]
|
||||
protected Producer $producer;
|
||||
|
||||
/**
|
||||
* 构造函数,初始化队列服务。
|
||||
*
|
||||
* @param DriverFactory $driverFactory 驱动工厂,用于获取具体的队列驱动实例
|
||||
*/
|
||||
public function __construct(DriverFactory $driverFactory)
|
||||
{
|
||||
$this->driver = $driverFactory->get('whf');
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建队列任务并执行。
|
||||
*
|
||||
* @param array $params
|
||||
* @return QueueService
|
||||
*/
|
||||
public function make(array $params): static
|
||||
{
|
||||
// 将当前用户信息添加到参数中
|
||||
$params['user'] = UserContext::getCurrentUser();
|
||||
|
||||
$this->params = $params;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将请求日志写入文件。
|
||||
*
|
||||
*/
|
||||
public function writeRequestLogs(): void
|
||||
{
|
||||
$this->driver->push(new RequestWriteLogsJob($this->params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 列配置保存.
|
||||
*/
|
||||
public function saveColumnConfig(): void
|
||||
{
|
||||
$this->driver->push(new ColumnConfigJob($this->params));
|
||||
}
|
||||
|
||||
/**
|
||||
* 将操作日志推送到队列中.
|
||||
*/
|
||||
public function opLogs(): void
|
||||
{
|
||||
// 将操作日志封装为OpLogsJob任务,并推送到驱动器对应的队列中
|
||||
$this->driver->push(new OpLogsJob($this->params));
|
||||
}
|
||||
}
|
48
app/Service/SysService.php
Normal file
48
app/Service/SysService.php
Normal file
@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午9:51
|
||||
* 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 Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2025/6/4
|
||||
* Time: 上午10: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 SysService
|
||||
{
|
||||
/**
|
||||
* 获取客户端IP信息
|
||||
* @param ServerRequestInterface $request
|
||||
* @return string
|
||||
*/
|
||||
public function getClientIpInfo(ServerRequestInterface $request): string
|
||||
{
|
||||
// 尝试从请求头获取客户端真实IP或转发的IP
|
||||
$realIP = $request->getHeaderLine('x-real-ip');
|
||||
$forwardedFor = $request->getHeaderLine('x-forwarded-for');
|
||||
// 确定客户端IP,优先使用 x-real-ip,其次是 x-forwarded-for,最后是默认IP
|
||||
$clientIP = $realIP ?: $forwardedFor;
|
||||
|
||||
return $clientIP ?: '127.0.0.1';
|
||||
}
|
||||
}
|
147
app/Service/Trait/ColumnConfigTrait.php
Executable file
147
app/Service/Trait/ColumnConfigTrait.php
Executable file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Author: ykxiao
|
||||
* Date: 2024/12/24
|
||||
* Time: 下午3:05
|
||||
* 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);
|
||||
/**
|
||||
* 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\Service\Trait;
|
||||
|
||||
use App\Context\UserContext;
|
||||
use App\Model\ColumnConfig;
|
||||
use App\Model\TableConfig;
|
||||
use App\Service\QueueService;
|
||||
use Exception;
|
||||
use Hyperf\Di\Annotation\Inject;
|
||||
use Hyperf\HttpServer\Router\Dispatched;
|
||||
use Hyperf\Stringable\Str;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
use function Hyperf\Collection\collect;
|
||||
use function Hyperf\Config\config;
|
||||
|
||||
trait ColumnConfigTrait
|
||||
{
|
||||
#[Inject]
|
||||
protected QueueService $queueService;
|
||||
|
||||
#[Inject]
|
||||
protected ServerRequestInterface $clientRequest;
|
||||
|
||||
/**
|
||||
* 获取列配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getColumnConfig(): array
|
||||
{
|
||||
$user = UserContext::getCurrentUser();
|
||||
if (! $user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$method = Str::snake($this->getCurrentAction());
|
||||
$this->saveColumnConfig($user, $method, $this->clientRequest->getParsedBody());
|
||||
|
||||
$this->saveTableConfig($user);
|
||||
// 从数据库中查询对应的列配置详情
|
||||
$fields = ColumnConfig::query()
|
||||
->where(['creator_id' => $user['id'], 'method' => $method])
|
||||
->select(['prop', 'label', 'sortable', 'sort', 'width', 'hide', 'fix', 'filter', 'is_search', 'search_type', 'condition'])
|
||||
->orderBy('sort')
|
||||
->get();
|
||||
return $fields->isNotEmpty() ? $fields->toArray() : $this->getDefaultConfig($method);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表格配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
public function getTableConfig(): array
|
||||
{
|
||||
$user = UserContext::getCurrentUser();
|
||||
if (! $user) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$config = TableConfig::query()
|
||||
->where(['creator_id' => $user['id'], 'method' => Str::snake($this->getCurrentAction())])
|
||||
->select(['size', 'config'])
|
||||
->first();
|
||||
|
||||
return $config ? $config->toArray() : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取默认配置.
|
||||
*/
|
||||
private function getDefaultConfig(string $method): array
|
||||
{
|
||||
$configList = config('column_config.' . $method);
|
||||
if (empty($configList)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return collect($configList)->map(function ($item, $key) {
|
||||
$item['sort'] = $key + 1;
|
||||
return $item;
|
||||
})->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前请求所匹配的方法.
|
||||
* @throws Exception
|
||||
*/
|
||||
private function getCurrentAction(): string
|
||||
{
|
||||
$dispatched = $this->clientRequest->getAttribute(Dispatched::class);
|
||||
return $dispatched->handler->callback[1] ?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 保存列配置.
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveColumnConfig(array $user, string $method, array $params): void
|
||||
{
|
||||
$this->queueService->make(compact('user', 'method', 'params'))->saveColumnConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function saveTableConfig(array $user): void
|
||||
{
|
||||
$body = $this->clientRequest->getParsedBody();
|
||||
if (empty($body['table_config'])) {
|
||||
return;
|
||||
}
|
||||
$query = TableConfig::query();
|
||||
$params = $body['table_config'];
|
||||
$method = Str::snake($this->getCurrentAction());
|
||||
$query->updateOrCreate(
|
||||
['creator_id' => $user['id'], 'method' => $method],
|
||||
[
|
||||
'method' => $method,
|
||||
'size' => $params['size'] ?? 'medium',
|
||||
'config' => $params['config'] ?? [],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user