diff --git a/backend/app/api/controller/v1/product/CenterProductController.php b/backend/app/api/controller/v1/product/CenterProductController.php index b583b3467..59d23990a 100644 --- a/backend/app/api/controller/v1/product/CenterProductController.php +++ b/backend/app/api/controller/v1/product/CenterProductController.php @@ -30,23 +30,57 @@ class CenterProductController */ public function productList(Request $request, CenterProductServices $services) { + /** @var \app\services\product\product\StoreCategoryServices $categoryServices */ + $categoryServices = app()->make(\app\services\product\product\StoreCategoryServices::class); $where = $request->getMore([ + [['sid', 'd'], 0], + [['cid', 'd'], 0], ['keyword', '', '', 'store_name'], ['priceOrder', ''], ['salesOrder', ''], + [['news', 'd'], 0], + [['selectId', 'd'], 0], + ['productId', ''], + ['coupon_category_id', ''], ['cate_id', ''], ['limit', 30], ['ids', ''], ['store_label_id', ''], ]); [$page, $limit] = $services->getPageValue(); - if (!empty($where['limit'])) { - $limit = (int)$where['limit']; + // limit 强制整型并设上限,避免异常大分页 + $limit = max(1, min(100, (int)($where['limit'] ?: $limit))); + unset($where['limit']); + // 排序字段白名单:只允许 asc/desc,其余清空(防 ORDER BY 注入) + foreach (['priceOrder', 'salesOrder'] as $orderKey) { + $orderVal = strtolower(trim((string)($where[$orderKey] ?? ''))); + $where[$orderKey] = in_array($orderVal, ['asc', 'desc'], true) ? $orderVal : ''; + if ($where[$orderKey] === '') { + unset($where[$orderKey]); + } } - // DIY 商品列表/商品选项卡「指定商品」「商品标签」:ids/store_label_id 逗号串转数组(DAO 按数组 whereIn/FIND_IN_SET 处理,ids 需按顺序排序) + // 分类页 selectId:有父级视为二级,否则一级 + if ($where['selectId'] && (!$where['sid'] || !$where['cid'])) { + if ($categoryServices->value(['id' => $where['selectId']], 'pid')) { + $where['sid'] = $where['selectId']; + } else { + $where['cid'] = $where['selectId']; + } + } + unset($where['selectId']); + // 品类/商品券落地页:productId → ids + if (!empty($where['productId'])) { + $where['ids'] = $where['productId']; + } + unset($where['productId']); + // 新品排序(前端 news=1) + if (!empty($where['news'])) { + $where['newsOrder'] = 'news'; + } + unset($where['news']); + // DIY「指定商品」「商品标签」:逗号串转整型数组 foreach (['ids', 'store_label_id'] as $idKey) { - $idValue = is_array($where[$idKey]) ? $where[$idKey] : explode(',', (string)$where[$idKey]); - $where[$idKey] = array_values(array_filter(array_map('intval', $idValue))); + $where[$idKey] = toIntArray($where[$idKey] ?? ''); if (empty($where[$idKey])) { unset($where[$idKey]); } @@ -55,13 +89,27 @@ class CenterProductController // 指定商品按已选数量返回,不受默认分页条数限制 $limit = max($limit, count($where['ids'])); } - if ($where['cate_id'] !== '') { - $where['cate_id'] = explode(',', $where['cate_id']); - foreach ($where['cate_id'] as $keys => &$items) { - $where['cate_id'][$keys] = (int)$items; + // 分类 / 品类券:统一整型化 + $where['cate_id'] = toIntArray($where['cate_id'] ?? ''); + $where['coupon_category_id'] = toIntArray($where['coupon_category_id'] ?? ''); + if (empty($where['cate_id'])) { + unset($where['cate_id']); + } + if (empty($where['coupon_category_id'])) { + unset($where['coupon_category_id']); + } + if (empty($where['sid'])) { + unset($where['sid']); + } + if (empty($where['cid'])) { + unset($where['cid']); + } + // 关键词截断,降低超长 LIKE 压力 + if (isset($where['store_name']) && is_string($where['store_name'])) { + $where['store_name'] = mb_substr(trim($where['store_name']), 0, 50); + if ($where['store_name'] === '') { + unset($where['store_name']); } - } else { - $where['cate_id'] = []; } $list = $services->getUserList($where, $page, $limit, (int)$request->uid()); diff --git a/backend/app/dao/product/CenterProductDao.php b/backend/app/dao/product/CenterProductDao.php index 17123cc87..d820a930a 100644 --- a/backend/app/dao/product/CenterProductDao.php +++ b/backend/app/dao/product/CenterProductDao.php @@ -40,23 +40,11 @@ class CenterProductDao extends BaseDao implements ProductQueryInterface public function getCount(array $where) { return $this->search($where, false)->when(isset($where['sid']) && $where['sid'], function ($query) use ($where) { - $query->whereIn('id', function ($query) use ($where) { - $query->name('store_product_cate')->where('cate_id', is_array($where['sid']) ? 'in' : '=', $where['sid'])->field('product_id')->select(); - }); + $this->applyCategoryFilter($query, $where['sid'], false); })->when(isset($where['cid']) && $where['cid'], function ($query) use ($where) { - $query->whereIn('id', function ($query) use ($where) { - $query->name('store_product_cate')->whereIn('cate_id', function ($query) use ($where) { - $query->name('store_category')->where('pid', is_array($where['cid']) ? 'in' : '=', $where['cid']) - ->whereOr('id', is_array($where['cid']) ? 'in' : '=', $where['cid'])->field('id')->select(); - })->field('product_id')->select(); - }); + $this->applyCategoryFilter($query, $where['cid'], true); })->when(!empty($where['coupon_category_id']), function ($query) use ($where) { - $where['coupon_category_id'] = toIntArray($where['coupon_category_id']); - $query->whereIn('id', function ($query) use ($where) { - $query->name('store_product_cate')->whereIn('cate_id', function ($query) use ($where) { - $query->name('store_category')->whereIn('pid', $where['coupon_category_id'])->field('id')->select(); - })->whereOr('cate_id', 'in', $where['coupon_category_id'])->field('product_id')->select(); - }); + $this->applyCategoryFilter($query, $where['coupon_category_id'], true); })->when(isset($where['ids']) && $where['ids'], function ($query) use ($where) { if ((isset($where['priceOrder']) && $where['priceOrder'] !== '') || (isset($where['salesOrder']) && $where['salesOrder'] !== '')) { $query->whereIn('id', $where['ids']); @@ -231,23 +219,11 @@ class CenterProductDao extends BaseDao implements ProductQueryInterface return $this->search($where, false)->with($with)->when($page != 0 && $limit != 0, function ($query) use ($page, $limit) { $query->page($page, $limit); })->when(isset($where['sid']) && $where['sid'], function ($query) use ($where) { - $query->whereIn('id', function ($query) use ($where) { - $query->name('store_product_cate')->where('cate_id', is_array($where['sid']) ? 'in' : '=', $where['sid'])->field('product_id')->select(); - }); + $this->applyCategoryFilter($query, $where['sid'], false); })->when(isset($where['cid']) && $where['cid'], function ($query) use ($where) { - $query->whereIn('id', function ($query) use ($where) { - $query->name('store_product_cate')->whereIn('cate_id', function ($query) use ($where) { - $query->name('store_category')->where('pid', is_array($where['cid']) ? 'in' : '=', $where['cid']) - ->whereOr('id', is_array($where['cid']) ? 'in' : '=', $where['cid'])->field('id')->select(); - })->field('product_id')->select(); - }); + $this->applyCategoryFilter($query, $where['cid'], true); })->when(!empty($where['coupon_category_id']), function ($query) use ($where) { - $where['coupon_category_id'] = toIntArray($where['coupon_category_id']); - $query->whereIn('id', function ($query) use ($where) { - $query->name('store_product_cate')->whereIn('cate_id', function ($query) use ($where) { - $query->name('store_category')->whereIn('pid', $where['coupon_category_id'])->field('id')->select(); - })->whereOr('cate_id', 'in', $where['coupon_category_id'])->field('product_id')->select(); - }); + $this->applyCategoryFilter($query, $where['coupon_category_id'], true); })->when(isset($where['ids']) && $where['ids'], function ($query) use ($where) { if ((isset($where['priceOrder']) && $where['priceOrder'] !== '') || (isset($where['salesOrder']) && $where['salesOrder'] !== '')) { $query->whereIn('id', $where['ids']); @@ -258,22 +234,12 @@ class CenterProductDao extends BaseDao implements ProductQueryInterface $query->whereNotIn('id', function ($query) { $query->name('live_goods')->where('is_del', 0)->where('audit_status', '<>', 3)->field('product_id')->select(); }); - })->when(isset($where['priceOrder']) && $where['priceOrder'] !== '', function ($query) use ($where) { - if ($where['priceOrder'] === 'desc') { - $query->order("price desc"); - } else { - $query->order("price asc"); - } - })->when(isset($where['newsOrder']) && $where['newsOrder'] !== '', function ($query) use ($where) { - if ($where['newsOrder'] === 'news') { - $query->order("id desc"); - } - })->when(isset($where['salesOrder']) && $where['salesOrder'] !== '', function ($query) use ($where) { - if ($where['salesOrder'] === 'desc') { - $query->order("sales desc"); - } else { - $query->order("sales asc"); - } + })->when(isset($where['priceOrder']) && in_array($where['priceOrder'], ['asc', 'desc'], true), function ($query) use ($where) { + $query->order('price ' . $where['priceOrder']); + })->when(isset($where['newsOrder']) && $where['newsOrder'] === 'news', function ($query) { + $query->order('id desc'); + })->when(isset($where['salesOrder']) && in_array($where['salesOrder'], ['asc', 'desc'], true), function ($query) use ($where) { + $query->order('sales ' . $where['salesOrder']); })->when(!isset($where['ids']), function ($query) use ($where) { if (isset($where['timeOrder']) && $where['timeOrder'] == 1) { $query->order('id desc'); @@ -282,10 +248,15 @@ class CenterProductDao extends BaseDao implements ProductQueryInterface } })->when(!$page && $limit, function ($query) use ($limit) { $query->limit($limit); - })->when(isset($where['store_label_id']), function ($query) use ($where) { - $query->where(function ($query) use ($where) { - foreach ($where['store_label_id'] as $value) { - $query->whereOr('FIND_IN_SET(:value, label_list)', ['value' => $value]); + })->when(!empty($where['store_label_id']), function ($query) use ($where) { + $labelIds = toIntArray($where['store_label_id']); + if (!$labelIds) { + return; + } + $query->where(function ($query) use ($labelIds) { + foreach ($labelIds as $index => $value) { + // 避免循环复用同名绑定导致 PDO 只取最后一次 + $query->whereOr('FIND_IN_SET(' . (int)$value . ', label_list)'); } }); })->order('sort_order desc')->field($field)->select()->toArray(); @@ -489,4 +460,41 @@ class CenterProductDao extends BaseDao implements ProductQueryInterface } return $list; } + + /** + * 按分类筛选商品(与下单品类券校验一致:商品表 cate_id) + * @param mixed $query + * @param mixed $categoryId 分类 id 或逗号串/数组 + * @param bool $expandChildren true=展开子分类(一级/品类券);false=仅匹配自身(二级 sid) + * @return void + */ + protected function applyCategoryFilter($query, $categoryId, bool $expandChildren = true): void + { + $cateIds = toIntArray($categoryId); + if (!$cateIds) { + $query->whereRaw('1 = 0'); + return; + } + if ($expandChildren) { + /** @var \app\dao\product\product\StoreCategoryDao $categoryDao */ + $categoryDao = app()->make(\app\dao\product\product\StoreCategoryDao::class); + $categoryRows = $categoryDao->getAllById($cateIds) ?: []; + $cateIds = array_values(array_unique(array_map('intval', array_column($categoryRows, 'id')))); + if (!$cateIds) { + $query->whereRaw('1 = 0'); + return; + } + } + $query->where(function ($q) use ($cateIds) { + foreach ($cateIds as $index => $cid) { + if ($index === 0) { + $q->whereFindInSet('cate_id', $cid); + } else { + $q->whereOr(function ($sub) use ($cid) { + $sub->whereFindInSet('cate_id', $cid); + }); + } + } + }); + } } diff --git a/backend/app/model/product/CenterProductModel.php b/backend/app/model/product/CenterProductModel.php index 335d8c958..37d399a1a 100644 --- a/backend/app/model/product/CenterProductModel.php +++ b/backend/app/model/product/CenterProductModel.php @@ -238,15 +238,34 @@ class CenterProductModel extends BaseModel */ public function searchCateIdAttr($query, $value) { - if ($value) { - if (is_array($value)) { - $query->whereIn('id', function ($query) use ($value) { - $query->name('store_product_cate')->where('cate_id', 'IN', $value)->whereOr('cate_pid', 'IN', $value)->field('product_id')->select(); - }); - } else { - $query->whereFindInSet('cate_id', $value); - } + if (!$value) { + return; } + $cateIds = toIntArray($value); + if (!$cateIds) { + return; + } + // 展开传入分类及其子分类,再按商品 cate_id 匹配(与列表/用券逻辑一致) + try { + $categoryRows = app()->make(\app\dao\product\product\StoreCategoryDao::class)->getAllById($cateIds) ?: []; + $expanded = array_values(array_unique(array_map('intval', array_column($categoryRows, 'id')))); + if ($expanded) { + $cateIds = $expanded; + } + } catch (\Throwable $e) { + // 分类服务不可用时回退为原始 id + } + $query->where(function ($q) use ($cateIds) { + foreach ($cateIds as $index => $cid) { + if ($index === 0) { + $q->whereFindInSet('cate_id', $cid); + } else { + $q->whereOr(function ($sub) use ($cid) { + $sub->whereFindInSet('cate_id', $cid); + }); + } + } + }); } /**