laravel 实现页面伪静态增强 seo
Laravel 中通过自定义分页器分页方法实现伪静态分页链接以利于 SEO
我们知道,Laravel 自带的分页器方法包含 simplePaginate 和 paginate 方法,一个返回不带页码的分页链接,另一个返回带页码的分页链接,但是这两种分页链接页码都是以带问号的动态参数形式附加在查询字符串中,形如 https://laravelacademy.org?page=100,但是这种包含动态参数的 URL 格式对 SEO 不友好,我们最好将其转化为 https://laravelacademy.org/page/100 这种不带问号的伪静态分页链接格式,遗憾的是 Laravel 并没有提供对这种伪静态分页链接格式的自定义支持,只好自己动手了。
我们将基于 paginate 方法实现的分页进行扩展,只是修改其分页链接格式,该方法底层调用了 Illuminate\Pagination\LengthAwarePaginator 类实现分页,所以我们需要自定义一个继承自该分页器类的 app/Utils/AcademyPaginator.php,详细如下看:
任务把传参的分页 url 改为 /page/11
http://www.24test.com/news/zhongchao?page=...
把 url 改为 /page/11
找到了
news 路由 指向与
D:\web\work\trunk\24_project\project_test\sports-transfer\module\Article\Controller\Web\type()
type 指向了 topic 方法
topic 中的分页是 直接使用了
$this->paging()
paging 位于 ArticlePaging 的一个 trait
分页的页码是直接使用了参数来获取的
$request = app(Request::class);
解决方案
改动路由,把参数改为路由占位符传参
找到路由文件
D:\web\work\trunk\24_project\project_test\sports-transfer\module\Article\Provider\RouteProvider.php--68line
我们直接加上路由参数
Route::get("/news/{type}/page/{pages}/", "ListController@type");// pagine 改为占位符
直接如下
修改分页样式
在 article.blade.php 中我们找到了引入了分页文件
位于:D:\web\work\trunk\24_project\project_test\sports-transfer\resources\views\pc\layout\block-pagination-style.blade.php
那我们在此处直接修改分页生产的 url
我们需要去找到类去修改 更为合适
直接修改分页类
D:\web\work\trunk\24_project\project_test\sports-transfer\vendor\laravel\framework\src\Illuminate\Pagination\AbstractPaginator.php--line 137
加上正则的判断
/** * Get the URL for a given page number. * * @param int $page * @return string */ // public function url($page) // { // if ($page <= 0) { // $page = 1; // } // // If we have any extra query string key / value pairs that need to be added // // onto the URL, we will put them in query string form and then attach it // // to the URL. This allows for extra information like sortings storage. // $parameters = [$this->pageName => $page]; // if (count($this->query) > 0) { // $parameters = array_merge($this->query, $parameters); // } // return $this->path // .(Str::contains($this->path, '?') ? '&' : '?') // .http_build_query($parameters, '', '&') // .$this->buildFragment(); // } /** * 重写页面 URL 实现代码,去掉分页中的问号,实现伪静态链接 * @access public * @param int $page * @since 1.1 * @return string */ public function url($page) { if ($page <= 0) { $page = 1; } // 移除路径尾部的/ $path = rtrim($this->path, '/'); // 如果路径中包含分页信息则正则替换页码,否则将页码信息追加到路径末尾 if (preg_match('/\/page\/\d+/', $path)) { $path = preg_replace('/\/page\/\d+/', '/page/' . $page, $path); } else { $path .= '/page/' . $page; } $this->path = $path; if ($this->query) { $url = $this->path . (Str::contains($this->path, '?') ? '&' : '?') . http_build_query($this->query, '', '&') . $this->buildFragment(); } elseif ($this->fragment) { $url = $this->path . $this->buildFragment(); } else { $url = $this->path; } return $url; }
它是在这里将分页信息作为查询参数的一部分放到分页链接中,所以我们需要在自定义的 AcademyPaginator 类中重写这个方法覆盖父类实现:
/** * 重写页面 URL 实现代码,去掉分页中的问号,实现伪静态链接 * @param int $page * @return string */public function url($page){ if ($page <= 0) { $page = 1; } // 移除路径尾部的/ $path = rtrim($this->path, '/'); // 如果路径中包含分页信息则正则替换页码,否则将页码信息追加到路径末尾 if (preg_match('/\/page\/\d+/', $path)) { $path = preg_replace('/\/page\/\d+/', '/page/' . $page, $path); } else { $path .= '/page/' . $page; } $this->path = $path; if ($this->query) { $url = $this->path . (Str::contains($this->path, '?') ? '&' : '?') . http_build_query($this->query, '', '&') . $this->buildFragment(); } elseif ($this->fragment) { $url = $this->path . $this->buildFragment(); } else { $url = $this->path; } return $url;}
至此,我们自定义的分页器类已经编写好了,接下来还要将这个分页器注册到模型类的查询构建器中以便可以像 simplePaginate 和 paginate 方法那样在模型实例上调用,这里我们需要借助查询构建器 Illuminate\Database\Eloquent\Builder 提供的静态 macro 方法在运行时动态扩展其提供的方法,我们还是在 AcademyPaginator 中定义这个方法扩展实现:
/** * 将新增的分页方法注册到查询构建器中,以便在模型实例上使用 * 注册方式: * 在 AppServiceProvider 的 boot 方法中注册:AcademyPaginator::rejectIntoBuilder(); * 使用方式: * 将之前代码中在模型实例上调用 paginate 方法改为调用 seoPaginate 方法即可: * Article::where('status', 1)->seoPaginate(15, ['*'], 'page', page); */public static function injectIntoBuilder(){ Builder::macro('seoPaginate', function ($perPage, $columns, $pageName, $page) { $perPage = $perPage ?: $this->model->getPerPage(); $items = ($total = $this->toBase()->getCountForPagination()) ? $this->forPage($page, $perPage)->get($columns) : $this->model->newCollection(); $options = [ 'path' => Paginator::resolveCurrentPath(), 'pageName' => $pageName, ]; return Container::getInstance()->makeWith(AcademyPaginator::class, compact( 'items', 'total', 'perPage', 'page', 'options' )); });}
这样,在模型实例上调用 seoPaginate 方法,将通过 AcademyPaginator 进行分页,最后我们在 AppServiceProvider 的 boot 方法中全局调用这个注入:
// 为查询构建器注入自己实现的分页器方法AcademyPaginator::injectIntoBuilder();接下来,就可以在自己的代码中编写以下这种代码实现伪静态分页链接了:$articles = Article::with('author', 'category')->public()->orderBy('id', 'desc')->seoPaginate($pageSize, $this->listColumns, 'page', $page);
致谢
https://www.cnblogs.com/liangzia/p/6223129...
为啥子 只要使用 seoPaginate?
因为
// 为查询构建器注入自己实现的分页器方法 // Builder 提供的静态macro 方法在运行时动态扩展 所以我们实现时候只要 // 将之前代码中在模型实例上调用 paginate 方法改为调用 seoPaginate 方法即可: // @author bobo // @date 2019-11-01
解决问题记录
本人代码是自己写的实现分页方法 直接 new 到了 laravel 底层的类传入属性
那我们这里不可以调用属性 seoPaginate
解决方法:
直接 new AcademyPaginator 类传入属性即可
/** * @param $articles Collection * @param int $pn * @return LengthAwarePaginator */ /** * topic * 分页 占位符方式传参 * 此处方法为兼容版本 担心其他地方调用报错 * @access public * @param mixed $arg1 列表页数据类型 * @param int $arg2 分页条目数 默认为空 * @param int $arg3 页码 默认为空 * @date 2019-11-01 * @author bobo<1576554465@qq.com> * @version $1.1$ * @since 1.0 * @return html */ public function paging($articles, $pn = 10,$page=null) { $request = app(Request::class); // dd($request); // 拿到页码 request有 if ($request->has('page')) { $pl = $request->input('page'); $pl = $pl <= 0 ? 1 :$pl; } elseif(!empty($page)) { // 传递的页码不为空则直接赋值 $pl = $page; $pl = $pl <= 0 ? 1 :$pl; } else { $pl = 1; } // $object = new LengthAwarePaginator($articles->slice(($pl-1)*$pn, $pn), $articles->count(), $pn, $pl,[ // 使用新方法seoPaginate $object = new AcademyPaginator($articles->slice(($pl-1)*$pn, $pn), $articles->count(), $pn, $pl,[ 'path' => Paginator::resolveCurrentPath(), 'pageName' => 'page', ]); $object->clazz = 'page'; return $object; }
排查其他受影响的 url
基本不影响,因为我们是适配使用的,只要你使用的是 laravel 提供的分页类即可。
而且路由没有进行大改