|
|
@@ -3,6 +3,7 @@
|
|
|
namespace Webkul\Shop\Http\Controllers\API;
|
|
|
|
|
|
use Illuminate\Http\JsonResponse;
|
|
|
+use Illuminate\Support\Facades\Cache;
|
|
|
use Webkul\Category\Repositories\CategoryRepository;
|
|
|
use Webkul\Core\Repositories\ChannelRepository;
|
|
|
use Webkul\Product\Repositories\ProductRepository;
|
|
|
@@ -10,6 +11,11 @@ use Webkul\Theme\Repositories\ThemeCustomizationRepository;
|
|
|
|
|
|
class HomeController extends APIController
|
|
|
{
|
|
|
+ /**
|
|
|
+ * 缓存 TTL(秒)
|
|
|
+ */
|
|
|
+ const CACHE_TTL = 300;
|
|
|
+
|
|
|
/**
|
|
|
* Create a new controller instance.
|
|
|
*/
|
|
|
@@ -30,6 +36,8 @@ class HomeController extends APIController
|
|
|
*/
|
|
|
public function index(): JsonResponse
|
|
|
{
|
|
|
+ $startTime = microtime(true);
|
|
|
+
|
|
|
$locale = core()->getRequestedLocaleCode();
|
|
|
|
|
|
// 通过 X-Channel header 区分 PC(default) / M(wap) 端
|
|
|
@@ -37,35 +45,48 @@ class HomeController extends APIController
|
|
|
$channel = $this->channelRepository->findOneByField('code', $channelCode)
|
|
|
?: core()->getCurrentChannel();
|
|
|
|
|
|
- // 1. 获取主题自定义数据(banner、分类轮播、产品轮播等)
|
|
|
- $customizations = $this->themeCustomizationRepository
|
|
|
- ->orderBy('sort_order')
|
|
|
- ->findWhere([
|
|
|
- 'status' => 1,
|
|
|
- 'channel_id' => $channel->id,
|
|
|
- 'theme_code' => $channel->theme,
|
|
|
- ]);
|
|
|
-
|
|
|
- $sections = [];
|
|
|
- foreach ($customizations as $item) {
|
|
|
- $section = $this->formatSection($item, $locale);
|
|
|
- if ($section !== null) {
|
|
|
- $sections[] = $section;
|
|
|
+ $cacheKey = 'home_api:' . $channel->code . ':' . $locale;
|
|
|
+
|
|
|
+ $data = Cache::remember($cacheKey, self::CACHE_TTL, function () use ($channel, $locale) {
|
|
|
+ // 1. 获取主题自定义数据(banner、分类轮播、产品轮播等)
|
|
|
+ $customizations = $this->themeCustomizationRepository
|
|
|
+ ->orderBy('sort_order')
|
|
|
+ ->findWhere([
|
|
|
+ 'status' => 1,
|
|
|
+ 'channel_id' => $channel->id,
|
|
|
+ 'theme_code' => $channel->theme,
|
|
|
+ ]);
|
|
|
+
|
|
|
+ $sections = [];
|
|
|
+ foreach ($customizations as $item) {
|
|
|
+ $section = $this->formatSection($item, $locale);
|
|
|
+ if ($section !== null) {
|
|
|
+ $sections[] = $section;
|
|
|
+ }
|
|
|
}
|
|
|
- }
|
|
|
|
|
|
- // 2. 获取分类树(供导航使用)
|
|
|
- $categories = $this->categoryRepository->getVisibleCategoryTree(
|
|
|
- $channel->root_category_id
|
|
|
- );
|
|
|
+ // 2. 获取分类树
|
|
|
+ $categories = Cache::remember(
|
|
|
+ 'home_categories:' . $channel->code . ':' . $locale,
|
|
|
+ self::CACHE_TTL,
|
|
|
+ fn () => $this->categoryRepository->getVisibleCategoryTree($channel->root_category_id)
|
|
|
+ );
|
|
|
|
|
|
- return response()->json([
|
|
|
- 'success' => true,
|
|
|
- 'data' => [
|
|
|
+ return [
|
|
|
'channel' => $channel->code,
|
|
|
'sections' => $sections,
|
|
|
'categories' => $this->formatCategoryTree($categories),
|
|
|
- ],
|
|
|
+ ];
|
|
|
+ });
|
|
|
+
|
|
|
+ $elapsed = round((microtime(true) - $startTime) * 1000);
|
|
|
+
|
|
|
+ // \Log::info("[Home API] {$channel->code}:{$locale} | 耗时: {$elapsed}ms");
|
|
|
+
|
|
|
+ return response()->json([
|
|
|
+ 'success' => true,
|
|
|
+ 'data' => $data,
|
|
|
+ '_debug' => ['elapsed_ms' => $elapsed],
|
|
|
]);
|
|
|
}
|
|
|
|
|
|
@@ -122,41 +143,68 @@ class HomeController extends APIController
|
|
|
*/
|
|
|
protected function getProductCarouselData(array $filters): array
|
|
|
{
|
|
|
- $params = [
|
|
|
- 'status' => 1,
|
|
|
- 'visible_individually'=> 1,
|
|
|
- 'limit' => $filters['limit'] ?? 12,
|
|
|
- 'sort' => $filters['sort'] ?? 'name-asc',
|
|
|
- ];
|
|
|
-
|
|
|
- if (! empty($filters['new'])) {
|
|
|
- $params['new'] = 1;
|
|
|
- }
|
|
|
-
|
|
|
- if (! empty($filters['featured'])) {
|
|
|
- $params['featured'] = 1;
|
|
|
- }
|
|
|
+ $cacheKey = 'home_products:' . md5(json_encode($filters));
|
|
|
+
|
|
|
+ return Cache::remember($cacheKey, self::CACHE_TTL, function () use ($filters) {
|
|
|
+ $params = [
|
|
|
+ 'status' => 1,
|
|
|
+ 'visible_individually'=> 1,
|
|
|
+ 'limit' => $filters['limit'] ?? 12,
|
|
|
+ 'sort' => $filters['sort'] ?? 'name-asc',
|
|
|
+ ];
|
|
|
|
|
|
- $products = $this->productRepository->getAll($params);
|
|
|
+ if (! empty($filters['new'])) {
|
|
|
+ $params['new'] = 1;
|
|
|
+ }
|
|
|
|
|
|
- return $products->map(function ($product) {
|
|
|
- $image = $product->images->first()
|
|
|
- ?? $product->base_image;
|
|
|
+ if (! empty($filters['featured'])) {
|
|
|
+ $params['featured'] = 1;
|
|
|
+ }
|
|
|
|
|
|
- return [
|
|
|
- 'id' => $product->id,
|
|
|
- 'sku' => $product->sku,
|
|
|
- 'name' => $product->name,
|
|
|
- 'slug' => $product->slug,
|
|
|
- 'type' => $product->type,
|
|
|
- 'price' => $product->getTypeInstance()->getMinimalPrice(),
|
|
|
- 'special_price' => $product->special_price,
|
|
|
- 'image_url' => $image->url ?? null,
|
|
|
- 'is_new' => (bool) ($product->new ?? false),
|
|
|
- 'is_featured' => (bool) ($product->featured ?? false),
|
|
|
- 'short_description'=> $product->short_description,
|
|
|
- ];
|
|
|
- })->values()->toArray();
|
|
|
+ // 使用 ProductRepository 查询,通过 params 传参,不走原生 SQL
|
|
|
+ $products = $this->productRepository
|
|
|
+ ->with(['images', 'reviews', 'price_indices'])
|
|
|
+ ->getAll($params);
|
|
|
+
|
|
|
+ // 批量加载 price_indices 后用内存计算 min_price,避免每个产品查一次
|
|
|
+ $customerGroupId = null;
|
|
|
+ try {
|
|
|
+ $customerGroupId = app(\Webkul\Customer\Repositories\CustomerRepository::class)
|
|
|
+ ->getCurrentGroup()->id;
|
|
|
+ } catch (\Exception $e) {
|
|
|
+ $customerGroupId = 1;
|
|
|
+ }
|
|
|
+ $channelId = core()->getCurrentChannel()->id;
|
|
|
+
|
|
|
+ return $products->map(function ($product) use ($customerGroupId, $channelId) {
|
|
|
+ $image = $product->images->first();
|
|
|
+ $approvedReviews = $product->reviews->where('status', 'approved');
|
|
|
+
|
|
|
+ // 直接从 price_indices 计算 min_price,避免调 getMinimalPrice()
|
|
|
+ $index = $product->price_indices
|
|
|
+ ->where('customer_group_id', $customerGroupId)
|
|
|
+ ->where('channel_id', $channelId)
|
|
|
+ ->first();
|
|
|
+ $minPrice = $index ? (float) $index->min_price : (float) $product->price;
|
|
|
+
|
|
|
+ return [
|
|
|
+ 'id' => $product->id,
|
|
|
+ 'sku' => $product->sku,
|
|
|
+ 'name' => $product->name,
|
|
|
+ 'slug' => $product->slug,
|
|
|
+ 'type' => $product->type,
|
|
|
+ 'price' => (float) $product->price,
|
|
|
+ 'min_price' => $minPrice,
|
|
|
+ 'special_price' => $product->special_price,
|
|
|
+ 'image_url' => $image->url ?? null,
|
|
|
+ 'is_new' => (bool) ($product->new ?? false),
|
|
|
+ 'is_featured' => (bool) ($product->featured ?? false),
|
|
|
+ 'short_description'=> $product->short_description,
|
|
|
+ 'rating' => round($approvedReviews->avg('rating') ?? 0, 1),
|
|
|
+ 'review_count' => $approvedReviews->count(),
|
|
|
+ ];
|
|
|
+ })->values()->toArray();
|
|
|
+ });
|
|
|
}
|
|
|
|
|
|
/**
|