Laravel 5.8 中生成唯一 Slug 的多种方法及最佳实践
2025-03-11 12:49:49
Laravel 5.8 中处理重复标题并生成唯一 Slug 的方法
遇到过标题相同,但需要生成唯一 Slug 的情况吗? 这篇博客就来聊聊在 Laravel 5.8 中如何解决这个问题。
问题
直接看问题:如果直接使用 Str::slug($postTitle, '-')
,标题一样的话,Slug 也会一样。我们想要的效果是,即使标题相同,Slug 也要是唯一的,比如:
原因分析
根本原因是Str::slug()
函数只会根据标题生成一个基本的 Slug。 如果存在相同的标题, 那么就会有相同的 URL, 这在数据库层面(如果 slug 字段设置了唯一索引) 和 SEO 角度来看,都是有问题的。
解决方案
有好几种办法可以解决这个问题,咱们一个个来看。
1. 使用循环 + 计数器
这是最直接也最容易理解的方法。基本思路是:
- 先用
Str::slug()
生成一个基础的 Slug。 - 检查数据库里有没有一样的 Slug。
- 如果有,就在 Slug 后面加上一个计数器(-1,-2,-3 这种)。
- 循环这个过程,直到找到一个唯一的 Slug。
原理: 通过递增计数器来保证 Slug 的唯一性。
代码示例:
use Illuminate\Support\Str;
use App\Post; // 假设你的 Post 模型是 App\Post
public function addPostDetails(Request $request)
{
// ... 其他代码 ...
$postTitle = $request->postTitle;
$posturl = Str::slug($postTitle, '-');
$count = 1;
$originalSlug = $posturl; //保存原始slug
while (Post::where('post_url', $posturl)->exists()) {
$posturl = $originalSlug . '-' . $count; // 拼接计数
$count++;
}
// ... 其他代码 ...
$savepost = new Post;
// ... 其他代码 ...
$savepost->post_url = $posturl;
$savepost->save();
// ... 其他代码 ...
}
说明:
Post::where('post_url', $posturl)->exists()
这个用来检查数据库里是不是已经存在这个 Slug。- 用了一个
while
循环, 保证了最终生成的 $posturl 是唯一值。
2. 使用 UUID 或 随机字符串
另一种方法是在 Slug 中加入 UUID(Universally Unique Identifier)或者一个随机字符串。
原理: UUID 和随机字符串几乎不可能重复,所以能保证 Slug 的唯一性。
代码示例 (UUID):
use Illuminate\Support\Str;
use App\Post;
public function addPostDetails(Request $request)
{
// ... 其他代码 ...
$postTitle = $request->postTitle;
$posturl = Str::slug($postTitle, '-') . '-' . Str::uuid();
// ... 其他代码 ...
$savepost = new Post;
// ... 其他代码 ...
$savepost->post_url = $posturl;
$savepost->save();
// ... 其他代码 ...
}
代码示例 (随机字符串):
use Illuminate\Support\Str;
use App\Post;
public function addPostDetails(Request $request)
{
// ... 其他代码 ...
$postTitle = $request->postTitle;
$posturl = Str::slug($postTitle, '-') . '-' . Str::random(8); // 8 个字符的随机字符串
// ... 其他代码 ...
$savepost = new Post;
// ... 其他代码 ...
$savepost->post_url = $posturl;
$savepost->save();
// ... 其他代码 ...
}
说明:
Str::uuid()
生成一个 UUID。Str::random(8)
生成一个包含 8 个随机字符的字符串。你可以根据需要调整长度。- 这种做法能有效保证slug唯一, 但可读性比较差, 用户或者搜索引擎不一定喜欢这样的url。
3. 循环 + 时间戳
第三种方案是把循环和时间戳结合。
原理: 时间戳是唯一的,把它加到 Slug 里能起到很好的区分作用。
代码示例:
use Illuminate\Support\Str;
use App\Post;
use Carbon\Carbon;
public function addPostDetails(Request $request)
{
// ... 其他代码 ...
$postTitle = $request->postTitle;
$posturl = Str::slug($postTitle, '-');
$originalSlug = $posturl;
while (Post::where('post_url', $posturl)->exists()) {
$posturl = $originalSlug. '-' . Carbon::now()->timestamp;
}
// ... 其他代码 ...
$savepost = new Post;
// ... 其他代码 ...
$savepost->post_url = $posturl;
$savepost->save();
// ... 其他代码 ...
}
说明 :
- 如果
Str::slug()
生成的 slug 不唯一, 会拼接上当前时间戳。 - 虽然理论上在同一毫秒内提交相同的标题仍会导致冲突,但是发生的概率极低。如果确实发生了,可以通过修改数据库该字段的唯一索引来实现。
4. 使用第三方包 (推荐)
如果不想自己写太多代码,可以用一些现成的第三方包,比如 cviebrock/eloquent-sluggable
。
原理: 这个包提供了很多方便的功能,可以自动生成唯一的 Slug,而且支持自定义。
安装:
composer require cviebrock/eloquent-sluggable
使用:
- 在你的
Post
模型里引入Sluggable
trait:
// app/Post.php
use Illuminate\Database\Eloquent\Model;
use Cviebrock\EloquentSluggable\Sluggable;
class Post extends Model
{
use Sluggable;
public function sluggable(): array
{
return [
'post_url' => [
'source' => 'post_title'
]
];
}
// ...其他代码...
}
-
在你的
config/app.php
文件里(通常不需要,但如果遇到问题,可以尝试):// ... 'providers' => [ // ... Cviebrock\EloquentSluggable\ServiceProvider::class, ], // ...
-
在数据库迁移文件里, 确保
post_url
字段是唯一的:// 假设你的表名是 posts Schema::table('posts', function (Blueprint $table) { $table->string('post_url')->unique(); });
-
在控制器里, 不用手动生成 Slug, 只需要保存
post_title
就行:
public function addPostDetails(Request $request)
{
// ... 其他代码 ...
$savepost = new Post;
$savepost->post_global_id = $postglobid;
$savepost->post_title = $postTitle;
$savepost->post_desc = $postDesc;
$savepost->post_img = $postimgnm;
$savepost->post_img_gal = json_encode($galImg);
// $savepost->post_url = $posturl; // 这行不需要了
$savepost->save();
// ... 其他代码 ...
}
说明:
'source' => 'post_title'
告诉这个包用post_title
字段来生成 Slug。- 这个包会自动处理重复的情况,生成唯一的 Slug。
eloquent-sluggable
还支持很多高级功能,比如自定义 Slug 生成规则,处理更新 Slug 等等,具体可以看它的文档。
进阶使用技巧(eloquent-sluggable
):
-
自定义 Slug 生成规则:
// app/Post.php public function sluggable(): array { return [ 'post_url' => [ 'source' => 'post_title', 'onUpdate' => true, //允许 title 更新时, 更新 slug. 默认是 false. 'unique' => true, //强制唯一. 'separator' => '-', // 更改分隔符 'maxLength'=> 200, //最大长度 'method' => function ($string, $separator) { // 完全自定义 slug 的生成 return 'prefix-' . Str::slug($string, $separator); } ] ]; }
详细可以参考官方文档。
-
手动触发slug生成 :
$post = Post::find(1); $post->resluggify(); $post->save();
安全建议:
无特殊安全建议,注意使用参数化查询或ORM以预防SQL注入即可。
总结
生成唯一 Slug 的方法很多,要根据自己的实际情况选择合适的方法. 如果图省事, 就直接上第三方包, 又快又好。 如果对URL美观度有要求,建议选择计数器或时间戳方式.