Laravel实现短URL模式获取分类/子分类/子子分类商品数据
实现Laravel分类短URL访问方案
问题背景
当前项目仅支持/{category}/{subcategory}/{childcategory}层级URL获取对应分类商品,需要实现/{category}、/{subcategory}、/{childcategory}短URL模式,直接访问对应层级分类的商品数据。现有控制器和路由代码如下:
现有控制器方法
public function category(Request $request, $slug = null, $slug1 = null, $slug2 = null) { if (Session::has('currency')) { $curr = Currency::find(Session::get('currency')); } else { $curr = Currency::where('is_default', '=', 1)->first(); } $cat = null; $subcat = null; $childcat = null; $minprice = (isset($request->min)) ? $request->min : 0; $maxprice = (isset($request->max)) ? $request->max : 0; $sort = $request->sort; $minprice = round(($minprice / $curr->value), 2); $maxprice = round(($maxprice / $curr->value), 2); if (!empty($slug)) { $cat = Category::where('slug', $slug)->firstOrFail(); $data['cat'] = $cat; $prodcs = Product::select( "*", DB::raw('(CASE WHEN size IS NULL THEN price ELSE size_base_price END) AS price_filter') ); $prodcss = Product::select( "*", DB::raw('(CASE WHEN size IS NULL THEN price ELSE size_base_price END) AS price_filter') ); $minamount = $prodcs->where('category_id', $cat->id)->orderBy('price_filter', 'ASC')->first()->price_filter; $maxamount = $prodcss->where('category_id', $cat->id)->orderBy('price_filter', 'DESC')->first()->price_filter; // $maxamount = $maxamount + 1; } if (!empty($slug1)) { $subcat = Subcategory::where('slug', $slug1)->firstOrFail(); $data['subcat'] = $subcat; $minamount = @Product::where('subcategory_id', $subcat->id)->orderBy('price', 'ASC')->first()->price; $maxamount = @Product::where('subcategory_id', $subcat->id)->orderBy('price', 'DESC')->first()->price; } if (!empty($slug2)) { $childcat = Childcategory::where('slug', $slug2)->firstOrFail(); $data['childcat'] = $childcat; $minamount = @Product::where('childcategory_id', $childcat->id)->orderBy('price', 'ASC')->first()->price; $maxamount = @Product::where('childcategory_id', $childcat->id)->orderBy('price', 'DESC')->first()->price; } $firstPrice = (isset($request->price[0])) ? $request->price[0] : ""; $secondPrice = (isset($request->price[1])) ? explode('-', $request->price[1]) : ""; $thirdPrice = (isset($request->price[2])) ? explode('-', $request->price[2]) : ""; $fourthPrice = (isset($request->price[3])) ? $request->price[3] : ""; $prods = Product::select( "*", DB::raw('(CASE WHEN size IS NULL THEN price ELSE size_base_price END) AS price_filter') ); $prods->when($cat, function ($query, $cat) { return $query->where('category_id', $cat->id); })->when($subcat, function ($query, $subcat) { return $query->where('subcategory_id', $subcat->id); })->when($childcat, function ($query, $childcat) { return $query->where('childcategory_id', $childcat->id); }); if (isset($request->price[0]) || isset($request->price[1]) || isset($request->price[2]) || isset($request->price[3])) { $prods->where(function ($query) use ($firstPrice, $secondPrice, $thirdPrice, $fourthPrice) { if ($firstPrice != "") { $query->having('price_filter', '<', $firstPrice); } if (!empty($secondPrice)) { $query->having('price_filter', [$secondPrice[0], $secondPrice[1]]); } if (!empty($thirdPrice)) { $query->having('price_filter', [$thirdPrice[0], $thirdPrice[1]]); } if ($fourthPrice != "") { $query->having('price_filter', '>', $fourthPrice); } }); } if (!isset($request->price[0]) && !isset($request->price[1]) && !isset($request->price[2]) && !isset($request->price[3])) { $prods->when($minprice, function ($query, $minprice) { return $query->having('price_filter', '>=', $minprice); }); $prods->when($maxprice, function ($query, $maxprice) { return $query->having('price_filter', '<=', $maxprice); }); } $prods->when($sort, function ($query, $sort) { if ($sort == 'date_desc') { return $query->orderBy('id', 'DESC'); } elseif ($sort == 'date_asc') { return $query->orderBy('id', 'ASC'); } elseif ($sort == 'price_desc') { return $query->orderBy('price_filter', 'DESC'); } elseif ($sort == 'price_asc') { return $query->orderBy('price_filter', 'ASC'); } })->when(empty($sort), function ($query, $sort) { return $query->orderBy('id', 'DESC'); }); $prods = $prods->where(function ($query) use ($cat, $subcat, $childcat, $request) { $flag = 0; if (!empty($cat)) { foreach ($cat->attributes as $key => $attribute) { $inname = $attribute->input_name; $chFilters = $request["$inname"]; if (!empty($chFilters)) { $flag = 1; foreach ($chFilters as $key => $chFilter) { if ($key == 0) { $query->where('attributes', 'like', '%' . '"' . $chFilter . '"' . '%'); } else { $query->orWhere('attributes', 'like', '%' . '"' . $chFilter . '"' . '%'); } } } } } if (!empty($subcat)) { foreach ($subcat->attributes as $attribute) { $inname = $attribute->input_name; $chFilters = $request["$inname"]; if (!empty($chFilters)) { $flag = 1; foreach ($chFilters as $key => $chFilter) { if ($key == 0 && $flag == 0) { $query->where('attributes', 'like', '%' . '"' . $chFilter . '"' . '%'); } else { $query->orWhere('attributes', 'like', '%' . '"' . $chFilter . '"' . '%'); } } } } } if (!empty($childcat)) { foreach ($childcat->attributes as $attribute) { $inname = $attribute->input_name; $chFilters = $request["$inname"]; if (!empty($chFilters)) { $flag = 1; foreach ($chFilters as $key => $chFilter) { if ($key == 0 && $flag == 0) { $query->where('attributes', 'like', '%' . '"' . $chFilter . '"' . '%'); } else { $query->orWhere('attributes', 'like', '%' . '"' . $chFilter . '"' . '%'); } } } } } }); $prods = $prods->where('status', 1)->get(); $prods = (new Collection(Product::filterProducts($prods)))->paginate(24); $data['prods'] = $prods; $data['minamount'] = $minamount; $data['maxamount'] = $maxamount; if ($request->ajax()) { $data['ajax_check'] = 1; return view('includes.product.filtered-products', $data); } return view('front.category', $data); }
现有路由代码
// CATEGORY SECTION Route::get('/{category?}/{subcategory?}/{childcategory?}','Front\CatalogController@category')->name('front.category'); Route::get('/{slug1}/{slug2}','Front\CatalogController@subcategory')->name('front.subcat'); Route::get('/{slug1}/{slug2}/{slug3}','Front\CatalogController@childcategory')->name('front.childcat'); Route::get('/categories/','Front\CatalogController@categories')->name('front.categories'); Route::get('/childcategories/{slug}', 'Front\CatalogController@childcategories')->name('front.childcategories'); // CATEGORY SECTION ENDS
解决方案
1. 路由调整
短URL路由需要优先匹配,同时移除原有冲突的子分类、子子分类路由:
// CATEGORY SECTION - 短URL路由放在最前面,优先匹配 Route::get('/{slug}', 'Front\CatalogController@singleCategory')->name('front.single.category'); // 保留原有多层级分类路由 Route::get('/{category}/{subcategory}/{childcategory}','Front\CatalogController@category')->name('front.category'); Route::get('/categories/','Front\CatalogController@categories')->name('front.categories'); Route::get('/childcategories/{slug}', 'Front\CatalogController@childcategories')->name('front.childcategories'); // 移除冲突路由 // Route::get('/{slug1}/{slug2}','Front\CatalogController@subcategory')->name('front.subcat'); // Route::get('/{slug1}/{slug2}/{slug3}','Front\CatalogController@childcategory')->name('front.childcat');
2. 新增短URL处理控制器方法
在Front\CatalogController中新增方法,匹配单段slug并路由到原有分类逻辑:
public function singleCategory(Request $request, $slug) { // 依次匹配顶级分类、子分类、子子分类 $cat = Category::where('slug', $slug)->first(); if ($cat) { return $this->category($request, $slug, null, null); } $subcat = Subcategory::where('slug', $slug)->first(); if ($subcat) { return $this->category($request, null, $slug, null); } $childcat = Childcategory::where('slug', $slug)->first(); if ($childcat) { return $this->category($request, null, null, $slug); } // 无匹配时返回404 abort(404); }
3. 优化原有控制器逻辑(可选)
原有代码中minamount/maxamount使用@抑制错误,且重复查询,可优化为:
// 替换原有分类下价格区间查询逻辑 $minamount = 0; $maxamount = 0; if ($cat) { $priceRange = Product::select( DB::raw('MIN(CASE WHEN size IS NULL THEN price ELSE size_base_price END) as min_price'), DB::raw('MAX(CASE WHEN size IS NULL THEN price ELSE size_base_price END) as max_price') )->where('category_id', $cat->id)->first(); $minamount = $priceRange->min_price ?? 0; $maxamount = $priceRange->max_price ?? 0; } elseif ($subcat) { $priceRange = Product::select( DB::raw('MIN(CASE WHEN size IS NULL THEN price ELSE size_base_price END) as min_price'), DB::raw('MAX(CASE WHEN size IS NULL THEN price ELSE size_base_price END) as max_price') )->where('subcategory_id', $subcat->id)->first(); $minamount = $priceRange->min_price ?? 0; $maxamount = $priceRange->max_price ?? 0; } elseif ($childcat) { $priceRange = Product::select( DB::raw('MIN(CASE WHEN size IS NULL THEN price ELSE size_base_price END) as min_price'), DB::raw('MAX(CASE WHEN size IS NULL THEN price ELSE size_base_price END) as max_price') )->where('childcategory_id', $childcat->id)->first(); $minamount = $priceRange->min_price ?? 0; $maxamount = $priceRange->max_price ?? 0; }
注意事项
- 路由优先级:短URL路由必须放在所有其他单段路由(如
/about、/contact)之前,避免被拦截。 - Slug唯一性:确保所有分类的slug全局唯一,否则会出现匹配错误;若无法保证,可在数据库增加层级标识字段辅助判断。
- 错误处理:无匹配分类时返回404,避免逻辑混乱。
内容的提问来源于stack exchange,提问作者ritesh malviya
相关产品推荐
相关产品推荐

