fix(商品列表): 分类/品类券按商品 cate_id 统一筛选,并加固排序与分页参数

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
吴昊天 2026-10-27 15:37:03 +08:00
parent f4d4c4377b
commit 8ecd8fcbf6
3 changed files with 144 additions and 69 deletions

View File

@ -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());

View File

@ -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);
});
}
}
});
}
}

View File

@ -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);
});
}
}
});
}
/**