小白折腾服务器(十一):postman 调试 API 查看当前接口全部 sql
laravel-debugbar、Clockwork、Telescope.......
等等等等,telescope是laravel作者开发的,功能强大界面美观没得说,但是laravel版本劝退(5.7.7+)
Clockwork和laravel-debugbar都必须是浏览器中调试,postman中就只能打log了....
有时候的sql又真的复杂,开发时查看orm组装的sql还是很有必要的,
打log有许多方法,但是我就是想看这一个接口的全部sql,我还不想去翻日志
想了想就折腾了这段代码
思路是这样的,
1.记日志(当有指定参数时,往指定的文件里写sql日志)
给全局加一个参数,dumpSql
,当有dumpSql参数时,app/Providers/AppServiceProvider.php
中的boot
方法里加DB::listen
往指定的文件里写sql日志
2.记日志之前清空该指定文件(保证每次记录的都是当前请求接口的sql日志)
这样就保证每次请求的api中的sql信息都是新的
3.响应中读取该指定文件,并将读取的内容组成数组,将该数组放入响应中
这里我用的是后置中间件,可以很方便的格式化响应数据,可以看这里:小白折腾服务器(七):自定义接口错误响应格式
思路很简单,看代码吧~
在app/Providers/AppServiceProvider.php
中添加代码:
public function boot()
{
$filename = storage_path('/logs/sql.log');
if (file_exists($filename)) {
unlink($filename);
}
// 只在本地开发环境启用 SQL 日志
if (app()->environment(['local', 'dev']) && request('dumpSql', 0)) {
DB::listen(function ($query) use ($filename) {
// $sql = vsprintf(str_replace("?", "'%s'", $query->sql), $query->bindings);
$sql = Str::replaceArray('?', $query->bindings, $query->sql);
$sqlArr = [
'date' => (string)now(),
'sql' => $sql,
'time' => $query->time,
];
$logPath = storage_path('/logs/');
if (!file_exists($logPath)) {
mkdir($logPath, 0777, true);
}
file_put_contents($filename, json_encode($sqlArr, 320) . PHP_EOL, FILE_APPEND);
});
}
}
在后置中间件中加一个私有方法:
这个方法是按行读取指定文件,并将内容装进数组
protected function readFileByLine($filename)
{
$fh = fopen($filename, 'r');
$sql = [];
while (!feof($fh)) {
$sql[] = json_decode(fgets($fh), true);
}
fclose($fh);
if (is_null(end($sql))) {
array_pop($sql);
}
return $sql;
}
修改后置中间件:
后置中间件格式化响应的时候,加上这3行代码,
当有dumpSql
参数 && 存在指定sql日志文件时,拼接sql响应参数。
$sqlFilename = storage_path('/logs/sql.log');
if (request('dumpSql', 0) && file_exists($sqlFilename)) {
$data['sql'] = $this->readFileByLine($sqlFilename);
}
可以看下效果:
全部的后置中间件代码如下:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Pagination\LengthAwarePaginator;
use Symfony\Component\HttpFoundation\BinaryFileResponse;
class After
{
/**
* 处理成功返回自定义格式
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
*
* @return mixed
*/
public function handle($request, Closure $next)
{
$response = $next($request);
if (is_array($response)) {
return $response;
}
// 如果是导出Excel类型直接返回
if ($response instanceof BinaryFileResponse) {
return $response;
}
// 执行动作
$oriData = $response->getOriginalContent();
$content = json_decode($response->getContent(), true) ?? $oriData;
$content = is_array($oriData) ? $oriData : $content;
if ($content['code'] ?? 0) {
return $response;
}
$data['data'] = isset($content['data']) ? $content['data'] : $content;
if ($content['meta'] ?? []) {
$data['meta'] = [
'total' => $content['meta']['total'],
'page' => $content['meta']['page'] ?? $content['meta']['current_page'] ?? 0,
'size' => $content['meta']['size'] ?? $content['meta']['per_page'] ?? 0,
];
}
if ($oriData instanceof LengthAwarePaginator) {
$data['meta'] = [
'total' => $content['total'],
'page' => $content['current_page'],
'size' => (int)$content['per_page'],
];
}
if ($data['data']['data'] ?? []) {
$data['data'] = $data['data']['data'];
}
$sqlFilename = storage_path('/logs/sql.log');
if (request('dumpSql', 0) && file_exists($sqlFilename)) {
$data['sql'] = $this->readFileByLine($sqlFilename);
}
$message = ['code' => 0, 'message' => 'success', 'data' => []];
$temp = ($content) ? array_merge($message, $data) : $message;
$response = $response instanceof JsonResponse ? $response->setData($temp) : $response->setContent($temp);
return $response;
}
/**
* @param $filename
*
* @return array
*/
protected function readFileByLine($filename)
{
$fh = fopen($filename, 'r');
$sql = [];
while (!feof($fh)) {
$sql[] = json_decode(fgets($fh), true);
}
fclose($fh);
if (is_null(end($sql))) {
array_pop($sql);
}
return $sql;
}
}
自己写的小方法,最大的好处是自己熟悉,少依赖,如果用别人的包,可能一更新还得等作者更新,
当然最大的好处是比较方便啦^_^
一定一定要加 app()->environment(['local', 'dev'])
这个判断呦
本作品采用《CC 协议》,转载必须注明作者和本文链接