overfirmament / over-util
A Laravel Util Package
Requires
- php: ^8.2
- ext-dom: *
- ext-iconv: *
- ext-json: *
- ext-mbstring: *
- ext-sockets: *
- ext-zlib: *
- laravel/framework: ^12.0 || ^13.0
- monolog/monolog: ^3.4
- php-amqplib/php-amqplib: ^3.7
- saloonphp/laravel-plugin: ^4.0
- saloonphp/saloon: ^4.0
- spatie/laravel-data: ^4.18
Requires (Dev)
- larastan/larastan: ^3.0
- laravel/pint: ^1.0
- orchestra/testbench: ^10.12 || ^11.3
- phpcompatibility/php-compatibility: ^9.3
- phpunit/phpunit: ^11.5 || ^12.5
- squizlabs/php_codesniffer: ^3.10
Suggests
None
Provides
None
Conflicts
None
Replaces
None
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,日志即可取到追踪号。
取值顺序(单条日志优先):
- 单条日志的
context - 单条日志的
extra - Laravel
Context Log::sharedContext()- 当前请求的 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 检查