Search by

overfirmament / over-util

overfirmament

A Laravel Util Package

Package info

github.com/overfirmament/over-util

pkg:composer/overfirmament/over-util

Statistics

Installs: 60

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v6.1.0 2026-11-06 11:43 UTC

README

个人使用的 Laravel 工具包。

环境要求

  • PHP 8.2 - 8.5
  • Laravel 12 或 13(PHP 8.2 使用 Laravel 12;Laravel 13 需要 PHP 8.3+)
  • PHP 扩展:dom、iconv、json、mbstring、sockets、zlib

安装

composer require overfirmament/over-util
php artisan overutil:install

overutil:install 会完成以下工作:

  • 发布 config/overutil.php、config/alarm.php、config/rabbitmq.php
  • 在 config/logging.php 的 channels 中写入 http_out、http_in、rabbit_mq
  • 向 app/Providers/AppServiceProvider.php 的 boot() 注入默认注释的 WorkerStopping 示例
  • 清理配置缓存

已存在的配置文件和日志 channel 默认跳过;使用 --force 覆盖:

php artisan overutil:install --force

只查看将要修改的内容、不写入文件:

php artisan overutil:install --dry-run

修改前会在 config/logging.php 旁生成 logging.php.overutil.bak 备份。向 AppServiceProvider.php 注入内容前也会生成 AppServiceProvider.php.overutil.bak 备份。

日志 channel 的定义位于包内部模板 src/Console/Stubs/logging-channels.stub,该文件不会发布到应用:安装命令把其中的 channel 写入 config/logging.php,未执行安装命令时 ServiceProvider 也会用同一份模板补充缺失的 channel。

Horizon / 常驻 worker

安装命令会在 app/Providers/AppServiceProvider.php 的 boot() 中注入一段默认注释的示例代码:

// over-util: rabbitmq-worker-stopping
// 可选:Horizon/队列 worker 停止时关闭 RabbitMQ 共享连接。
// 确认需要后,取消下面代码的注释即可启用。
// \Illuminate\Support\Facades\Event::listen(
//     \Illuminate\Queue\Events\WorkerStopping::class,
//     static function (\Illuminate\Queue\Events\WorkerStopping $event): void {
//         app(\Overfirmament\OverUtils\ToolBox\RabbitMqConnectionManager::class)->disconnect();
//     }
// );

默认不会启用。Horizon 等常驻 worker 如果需要优雅退出时关闭共享连接,取消注释即可;只关闭指定连接时,把 disconnect() 改为 disconnect('thread_update')。安装命令会根据 over-util: rabbitmq-worker-stopping 标记幂等跳过,不会重复注入或覆盖已经修改过的代码。

日志

安装命令写入的 channel 如下:

'http_out' => [
    'driver' => 'daily',
    'path' => storage_path('logs/http/out_info.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 14,
    'formatter' => \Overfirmament\OverUtils\Logger\LogFormatter::class,
],

'http_in' => [
    'driver' => 'daily',
    'path' => storage_path('logs/http/in_info.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 14,
    'formatter' => \Overfirmament\OverUtils\Logger\LogFormatter::class,
],

'rabbit_mq' => [
    'driver' => 'daily',
    'path' => storage_path('logs/rabbit/debug.log'),
    'level' => env('LOG_LEVEL', 'debug'),
    'days' => 14,
    'formatter' => \Overfirmament\OverUtils\Logger\LogFormatter::class,
],

LogFormatter 会自动把追踪字段提到 JSON 顶层,并对 GBK 字符串做 UTF-8 转换,避免日志写入失败。

追踪字段

追踪字段统一由 Overfirmament\OverUtils\Support\TraceContext 解析,LogFormatter、HelperUtil::getContextTraceId()、ApiResponse、AlarmUtil 都通过它读取,不再各自硬编码 trace_id。

在 config/overutil.php(或 .env)中配置:

'trace' => [
    // 中间件/业务代码写入的字段,如 request_id、requestId
    'field'  => env('OVERUTIL_TRACE_FIELD', 'trace_id'),
    // 日志与 API 响应中输出的字段
    'output' => env('OVERUTIL_TRACE_OUTPUT', 'trace_id'),
],

例如项目使用 $request->merge(['request_id' => $uuid]),只需把 field 配成 request_id,日志即可取到追踪号。

取值顺序(单条日志优先):

  1. 单条日志的 context
  2. 单条日志的 extra
  3. Laravel Context
  4. Log::sharedContext()
  5. 当前请求的 input / 路由参数(非 console 环境)

候选键始终是联合的:配置字段、它的 snake_case/camelCase 变体,以及兼容保留的 trace_id / traceId。因此切换配置不会影响历史日志的解析。

自定义 formatter 时可覆盖字段:

$formatter = new \Overfirmament\OverUtils\Logger\LogFormatter;
$formatter->setTrackField('requestId')->setOutputField('request_id');

Saloon 请求/响应日志

包内注册了 Saloon 全局中间件,开启后会记录所有 Saloon 出站请求、响应和连接级失败。默认关闭:

OVERUTIL_SALOON_LOG=true

配置位于 config/overutil.php:

'saloon' => [
    'logging' => [
        'enabled' => env('OVERUTIL_SALOON_LOG', false),
        'channel' => env('OVERUTIL_SALOON_LOG_CHANNEL', 'http_out'),
        'level' => env('OVERUTIL_SALOON_LOG_LEVEL', 'info'),
        'log_request' => true,
        'log_response' => true,
        'log_fatal' => true,
        'log_headers' => false,
        'log_body' => true,
        'max_body_length' => 2000,
        'redacted_headers' => ['authorization', 'cookie', 'x-api-key'],
        'redacted_keys' => ['password', 'token', 'access_token', 'secret'],
        // 为空表示所有 Connector,支持通配符
        'connectors' => [],
        'except_connectors' => [],
    ],
    'trace' => [
        'header' => env('OVERUTIL_SALOON_TRACE_HEADER'), // 如 X-Request-Id
        'propagate' => env('OVERUTIL_SALOON_PROPAGATE_TRACE', true),
    ],
],

行为说明:

  • 只影响 Saloon 请求,不影响 Laravel 入站请求或其他 HTTP 客户端。
  • 默认记录请求、响应两条日志;headers 默认关闭,body 自动脱敏并按 max_body_length 截断。
  • trace 取值顺序:请求头(trace.header)→ TraceContext::resolve()。显式请求头优先,便于单个调用指定 trace。
  • propagate=true 时,若请求头不存在,会把解析到的 trace 注入下游请求;已存在则不覆盖。
  • 队列 Job 中使用 Context::add($field, $id) 后,Laravel 会自动随队列传播;手动执行的 artisan 命令需要自行建立 Context。

API 响应

ApiResponse trait 将业务码(statusCode / body 里的 code)与 HTTP 状态码(httpCode)完全隔离:

  • 两者默认值都是 200。
  • 业务码不会参与 HTTP 状态的判断,包括 400、500 这类看起来像 HTTP 码的值。
  • 需要非 200 的 HTTP 状态时,只能显式指定:setHttpCode(),或方法的 $httpCode 形参。
use Overfirmament\OverUtils\Traits\ApiResponse;

class Controller
{
    use ApiResponse;

    public function show()
    {
        return $this->success(['id' => 1]);           // HTTP 200,code 200
        // return $this->created($data);              // HTTP 201
        // return $this->deleted();                   // HTTP 204
        // return $this->notFound();                  // HTTP 404,code 404
        // return $this->failed('失败', 400408);       // HTTP 200,code 400408
        // return $this->failed('失败', 400408, 'error', 400); // HTTP 400,code 400408
        // return $this->setHttpCode(400)->failed('失败', 400408); // HTTP 400
        // return $this->respond(['ok' => true], httpCode: 202);   // HTTP 202
    }
}

语义化快捷方法的默认 HTTP 状态是显式写死的,而不是从业务码推导:notFound() 404、error() 422、internalError() 500、created() 201、deleted() 204;都可以通过第二个(或对应的)$httpCode 形参覆盖。

ApiResponse 是有状态的响应构建器:statusCode、httpCode、isJsonP 等都会保留在实例上。一个实例只应产出一个响应;批量场景请为每条结果创建新实例,或使用 setHttpCode() / $httpCode 形参显式控制。快捷方法不会把 201/204/400 等写回实例的 httpCode,只有 setHttpCode() 会。

统一响应字段名

ApiResponse 默认使用 message 作为提示字段,旧系统通常使用 messages。 在 .env 中设置即可全局切换,无需修改任何类:

OVERUTIL_RESPONSE_MESSAGE_FIELD=messages

也可以发布配置后修改 config/overutil.php:

'response' => [
    'message_field' => 'messages',
],

修改 .env 后,如果项目启用了 php artisan config:cache,需要执行 php artisan config:clear 才会生效。

个别接口仍可单次覆盖:

return $this->messageField('msg')->message('...');

如果项目不使用配置文件,也可以在基类覆写默认值:

protected function defaultMessageField(): string
{
    return 'messages';
}

JWT / Token 过期时间

respondWithToken() 的第三个参数支持秒数、DateTimeInterface、DateInterval。不传时会自动尝试解析当前 guard 的过期时间:

return $this->respondWithToken($token);

需要指定 guard 时:

return $this->respondWithToken($token, 'Bearer', null, 'api');

兼容 tymon/jwt-auth 的 auth()->guard('api')->factory()->getTTL()(单位:分钟),也支持 Sanctum、Passport 的配置回退。包本身不依赖 tymon/jwt-auth。

也可以显式传入秒数:

return $this->respondWithToken(
    $token,
    'Bearer',
    auth()->guard('api')->factory()->getTTL() * 60
);

RabbitMQ

配置文件 config/rabbitmq.php 支持多个连接,默认使用 default 连接。典型的循环发送写法:

use Overfirmament\OverUtils\ToolBox\RabbitMqUtil;

foreach ($messages as $message) {
    RabbitMqUtil::getInstance()
        ->autoBuild()
        ->message(json_encode($message))
        ->publish();
}

getInstance() 每次返回新的轻量对象,底层 AMQPStreamConnection 由 RabbitMqConnectionManager 按连接名缓存复用。publish() 只关闭当前 channel,不会关闭共享连接,因此可以在外部 foreach 中安全使用。

rabbit_mq 日志使用两个 ID 关联一次发送:

  • connect_id:连接级 ID。同一个缓存连接复用期间保持不变,包括多个 RabbitMqUtil 实例共用连接;断开或重连后更换。
  • mq_trace_id:channel 级 ID。每次创建新 channel 时生成,publish() 结束会关闭 channel,所以每次发送都有独立的值。
mqTool初始化 connect_id=8f3a... 使用配置rabbitmq.connections.default
exchange构建完成 connect_id=8f3a... mq_trace_id=1c9b...
message构建完成 connect_id=8f3a... mq_trace_id=1c9b...
msg.publish connect_id=8f3a... mq_trace_id=1c9b...
msg.ack connect_id=8f3a... mq_trace_id=1c9b...

同时会写入日志 context:

{
    "connect_id": "8f3a...",
    "mq_trace_id": "1c9b..."
}

mq_trace_id 不会被 LogFormatter 提升为顶层 trace_id,不影响请求级链路追踪。也可以通过 getConnectId() / getMqTraceId() 在业务代码中读取。

需要主动释放连接时:

$mq = RabbitMqUtil::getInstance();
// ...
$mq->closeConnection();

也可以设置:

'publish' => [
    // ...
    'close_connection_after_publish' => true,
],

企业微信报警

use Overfirmament\OverUtils\ToolBox\AlarmUtil;

AlarmUtil::init() // 默认读取 alarm.qy_wx.default
    ->text('标题', '内容');

可配置多个机器人:

AlarmUtil::init('ops')->text('告警', '服务异常');

需要限流时:

AlarmUtil::init()
    ->setLimit(3, 'order-sync', 300)
    ->text('告警', '订单同步失败');

常用命令

php artisan overutil:cache-reset   # 清理路由缓存并重建
php artisan publish:cache          # 兼容旧名称

开发

composer test          # PHPUnit
composer analyse       # PHPStan + Larastan
composer format:test   # Pint 检查
vendor/bin/phpcs       # PHPCompatibility 检查