Laravel-admin 在 Laravels 运行的的一些问题解析——记一次因为莽而付出的代价
初生牛犊不怕虎,直接莽上laravels
前因
我们公司一直都是采用 think3.2 ,虽然使用起来确实不错,但是哪能有 laravel 舒服
于是我才用 laravel ,但是经理觉得后台太慢了
我做了一部分优化还是慢,我太难了,之前他让我随便选框架
我就想到了大佬的 [laravels](https://github.com/hhxsv5/laravel-s) 心里想laravels+laravel-admin岂不是美哉
结果一使用出
大问题
发现问题,问题介绍
前端问题
laravel-s
已提供Cleaner,请参阅文档使用
- 第一个问题就是比较常见的,顶部的刷新无限增多,如图
- 第二个问题就是,删除
这个
却进入上一个进入的删除路由,导致各种删除失败
- 导出excel提示
exit
- Pjax中间件提示
exit
解决问题,提供思路
授人以鱼不如授人以渔
我们先排查第一个问题,这个比较常见,其实就是我们的Admin
实例并没有被清除,导致无限添加navBar
然后在 admin/bootstrap.php
中每次都调用一下
于是我想到第一个办法,这个方法比较
憨批
,因为当时认为这个实例重新创建应该挺麻烦,所以采用局部清理的方式
解决问题之后测试删除功能,又发现新的问题,也就是
问题2
这个问题很明显是实例没清理干净,导致上一步生成的js下一步仍然存在,排除在控制器生成的可能,
所以初步判断只有可能存在于\Encore\Admin\Admin
实例中,因为这个实例是存在于容器中
所以直接从中寻找,发现原来是\Encore\Admin\Admin
静态变量
//因为是表单所以我们先进入 Encore\Admin\Form\Form 寻找Tool 发现renderDelete
protected function renderDelete()
{
$trans = [
'delete_confirm' => trans('admin.delete_confirm'),
'confirm' => trans('admin.confirm'),
'cancel' => trans('admin.cancel'),
'delete' => trans('admin.delete'),
];
.
.
.
Admin::script($script);
return <<<HTML
<div class="btn-group pull-right" style="margin-right: 5px">
<a href="javascript:void(0);" class="btn btn-sm btn-danger {$class}-delete" title="{$trans['delete']}">
<i class="fa fa-trash"></i><span class="hidden-xs"> {$trans['delete']}</span>
</a>\</div>
HTML;
}
看到
Admin::script
这个原来是在Admin 下的HasAssets
/**
* @param string $script
* @param bool $deferred
* @return array|\Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public static function script($script = '', $deferred = false)
{
if (!empty($script)) {
if ($deferred) {
return self::$deferredScript = array_merge(self::$deferredScript, (array) $script);
}
return self::$script = array_merge(self::$script, (array) $script);
}
$script = array_unique(array_merge(static::$script, static::$deferredScript));
return view('admin::partials.script', compact('script'));
}
那么解决起来就狠方便了 直接在bootstrap
中初始化一下这些静态变量即可,尽量少改源代码
所以自己实现一个
clean
, laravel-s已提供Cleaner
这里仅供参考
\Encore\Admin\Admin::$script=[];
\Encore\Admin\Admin::$deferredScript=[];
\Encore\Admin\Admin::$headerJs = [];
\Encore\Admin\Admin::$manifestData = [];
\Encore\Admin\Admin::$extensions = [];
.
.
.
$app->forgetInstance(\Encore\Admin\Admin::class);
Facade::clearResolvedInstance(\Encore\Admin\Admin::class);
由此,`问题一`和`问题二`已经基本解决
下面就是解决
exit
问题了,采用抛出异常
的方式
//csv导出操作
$res = Response::stream(function () {
$handle = fopen('php://output', 'w');
$titles = [];
$this->chunk(function ($records) use ($handle, &$titles) {
if (empty($titles)) {
$titles = $this->getHeaderRowFromRecords($records);
// Add CSV headers
fputcsv($handle, $titles);
}
foreach ($records as $record) {
fputcsv($handle, $this->getFormattedRecord($record));
}
});
// Close the output stream
fclose($handle);
}, 200, $headers);
swoole_exit($res);
//Pjax 修改后
$next = function () use ($response) {
return $response;
};
swoole_exit((new static())->handle(Request::capture(), $next));
//swoole_exit实际是封装的抛出异常
if (!function_exists('swoole_exit')){
function swoole_exit($response)
{
throw new App\Exceptions\SwooleExitException($response);
}
}
我们需要修改异常处理
Handler
<?php
namespace App\Exceptions;
use Exception;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Routing\Router;
use Illuminate\Support\Facades\Response;
use Symfony\Component\HttpFoundation\Response as SymfonyResponse;
class Handler extends ExceptionHandler\
{
/**
* A list of the exception types that are not reported.\ *\ * @var array
*/
protected $dontReport = [
SwooleExitException::class
];
/**
* A list of the inputs that are never flashed for validation exceptions.\ *\ * @var array
*/
protected $dontFlash = [
'password',
'password_confirmation',
];
/**
* Report or log an exception@param \Exception $exception\
* @return void\
*/
public function report(Exception $exception)
{
parent::report($exception);
}
/**
* Render an exception into an HTTP response.\ *\ * @param \Illuminate\Http\Request $request
* @param \Exception $exception
* @return \Illuminate\Http\Response
*/
public function render($request, Exception $exception)
{
//判断是否为我们的自定义异常
if ($exception instanceof SwooleExitException) {
//直接调用perpare
return $exception->getResponse()->prepare($request);
}
return parent::render($request, $exception);
}
}
自定义异常代码
SwooleExitException
<?php
namespace App\Exceptions;
use Exception;
use Throwable;
class SwooleExitException extends Exception\
{
protected $response;
public function __construct($response,$message = "", $code = 0, Throwable $previous = null)
{
$this->response = $response;
parent::__construct($message, $code, $previous);
}
//获取响应内容
public function getResponse(){
return $this->response;
}
如果你直接把exit去掉,那么 导出csv 会提示你 oops … ob_end_clean() 这个报错
我们可以在Hhxsv5\LaravelS\Illuminate\Laravel
中的 handleDynamic
加一个判断ob_get_length()
也可以不加,因为后期解决exit退出问题,也就不会有这种报错
解决思路
因为exit其实就是不执行后续的响应
,所以我们想到异常就是执行到异常抛出之前,于是我们可以定义一种特定的异常,来提前结束,用来代替exit ,至于 swoole/laravels 为什么不能使用这些函数我就不在这赘述
使用异常,但是我们也得返回请求,但是我们的请求不能直接send掉,因为laravels需要使用swoole的方式返回,我们直接执行 send 是不起作用的,那我们就可以通过特定的异常返回我们所需要的响应,
public static function toResponse($request, $response)\
{
if ($response instanceof Responsable) {
$response = $response->toResponse($request);
}
if ($response instanceof PsrResponseInterface) {
$response = (new HttpFoundationFactory)->createResponse($response);
} elseif ($response instanceof Model && $response->wasRecentlyCreated) {
$response = new JsonResponse($response, 201);
} elseif (! $response instanceof SymfonyResponse &&
($response instanceof Arrayable ||
$response instanceof Jsonable ||
$response instanceof ArrayObject ||
$response instanceof JsonSerializable ||
is_array($response))) {
$response = new JsonResponse($response);
} elseif (! $response instanceof SymfonyResponse) {
$response = new Response($response);
}
if ($response->getStatusCode() === Response::HTTP_NOT_MODIFIED) {
$response->setNotModified();
}
return $response->prepare($request);
}
Router::toResponse();
$exception->prepare()
两种方式都可以,不过推荐使用后者
出现json格式异常/其他异常,主要是因为 其他扩展可能有替换Handler/自定义Handler没有进行处理,需要额外注意
感谢z-song
提供的laravel-admin优质扩展
感谢hhxsv5
提供的laravel-s优质扩展
具体分析请等下次更新,我们把laravels
大致走一遍,学习一下(🕊)
本作品采用《CC 协议》,转载必须注明作者和本文链接
如果使用了dingo,记得修改dingo的handle,否则会出现json的报错
LaravelS 已内置 Laravel Admin Cleaner,打开即可。
execl导出$this->download($this->fileName)->prepare(request())->send();
替换为swoole_exit($this->download($this->fileName));
推荐文章: