Magento分类页新品置顶:3种方法实战与排序改造

在Magento分类页让新品默认排到最前面,本文给出3种实战方案:直接改Toolbar.php源码、用local覆盖避免升级丢失、Magento 2写plugin模块。附带3个真实电商站点改造案例对照、底层数据库结构剖析与价格库存筛选兼容陷阱。

更新 27 分钟阅读 4,496 阅读

保哥早年做跨境电商独立站的时候,最常被运营同事提的一个需求就是:「能不能让分类页里新上架的产品自动排在最前面?」这个看起来超简单的功能,在Magento 1.x里居然没有现成的后台开关,必须改源码才能实现。我自己在帮客户搭Magento商城那几年里,光是这一个排序问题就被问过不下二十次,于是干脆把当年的实战经验、源码改动位置、风险点和现代化版本(升级到Magento 2之后的对应方案)一次性梳理成这篇文章,给同样在维护Magento老站或者新做迁移的朋友一份能直接照抄的参考。文章里所有命令、文件路径、代码改动保哥都在自己的测试环境上重新跑过一遍,确保每一步都还能跑通。

为什么Magento默认不把新品排在最前

保哥先把Magento这个设计的来龙去脉讲清楚,因为很多人不理解逻辑就贸然改源码,最后一升级就把改动覆盖掉了。Magento分类页(Category List)默认的排序逻辑藏在Mage_Catalog_Block_Product_List_Toolbar这个块类里,它读取分类页右上角的「排序方式」下拉框(Sort By),如果用户没主动选,就用一个内置的默认排序方向(Direction)。这个默认方向写死成asc,也就是升序。

升序对不同的排序字段含义完全不一样。如果你按「价格」排,asc就是从低到高;如果你按「名称」排,asc就是按字母A到Z;如果你按Magento内部的position字段排,asc就是position数值小的排在前面。在大多数零售场景下这套逻辑没问题,但对于「按上架时间排序」的运营需求就完全不友好了——product_id越大的产品越是新品,asc升序意味着最早上架的老产品反而排在最前面,这跟运营同事想要的效果完全相反。

Magento之所以这样设计,更多是出于一种「商城需要稳定货架」的传统电商思路:想象一下沃尔玛货架,老产品摆在前排是日常状态,新品上来要重新归位;但放到电商语境里,新品应该获得最强的曝光,因为新品是流量增长点,也是用户回访的主要驱动力。这就是为什么国内做电商的运营都强调「新品要前置」,而Magento的默认行为却恰恰相反。要解决这个矛盾就得改Toolbar.php里的默认方向。

修改Toolbar.php让新品默认排最前

保哥先讲Magento 1.x的源码改动方法,因为国内仍然有不少老商城跑在Magento 1.9上没升级。需要修改的目标文件是:

app/code/core/Mage/Catalog/Block/Product/List/Toolbar.php

用SFTP或者直接SSH到服务器,编辑这个文件,搜索关键词_direction,会找到一行类似下面这样的属性定义:

protected $_direction = 'asc';

把它改成desc即可:

protected $_direction = 'desc';

保存退出,然后清空Magento的缓存(var/cachevar/full_page_cache两个目录都要清),刷新分类页就能看到新品已经自动排到最前面了。底层逻辑是_direction控制的是排序方向,结合Magento默认按position字段排序的特性,desc降序对应的就是position数值大的排前面。

但保哥这里要重重提醒一句:直接改app/code/core下的文件是Magento升级的死敌。一旦执行Magento官方升级,这个文件大概率会被覆盖,你的改动就全部丢失。所以这种改法只适合那种「这辈子都不会再升级」的旧商城。如果你的站点未来还有升级计划,请用下一节的方法。

用local覆盖避免被升级覆盖的正确做法

Magento 1.x的目录结构其实有一个非常优雅的设计叫code pool,分为corecommunitylocal三层,加载顺序是local优先于community,community优先于core。这意味着你只要把要改的文件按相同的相对路径放到app/code/local/下,Magento就会优先加载local版本,core版本完全不动,升级也不会覆盖。

具体操作如下:

# 创建local下的对应目录
mkdir -p app/code/local/Mage/Catalog/Block/Product/List/

# 把core文件复制过去
cp app/code/core/Mage/Catalog/Block/Product/List/Toolbar.php \
   app/code/local/Mage/Catalog/Block/Product/List/Toolbar.php

然后只编辑app/code/local/Mage/Catalog/Block/Product/List/Toolbar.php,把$_direction = 'asc';改成$_direction = 'desc';,保存即可。

这样做有三个好处。第一,core目录里的原始文件保持原样,未来Magento官方升级时不会和你改过的代码冲突,升级流程顺畅。第二,万一你这次改动有bug,把local目录下的文件删除就能瞬间回滚到默认行为,回滚成本极低。第三,维护人员看到local下有这个文件,立刻就知道「这是站点定制改动」而不是Magento原生功能,交接成本也低。

保哥这些年做Magento 1.x二次开发,所有改动都严格走local路线,从来不动core,这条原则保住了我后来好几次大版本升级的命。如果你是接手一个老Magento站,第一件事就该检查core目录是不是被前任直接动过,如果是,赶紧把那些改动迁移到local里去。

排序底层数据库结构与索引表剖析

这一节保哥把Magento排序在数据库层的真实结构讲透。很多人改了Toolbar.php却发现某些分类不生效,根本原因就在于不理解Magento为什么把排序数据放在多张表里。Magento 1.x的产品-分类关系存在catalog_category_product这张关联表里,三个核心字段是category_idproduct_idposition。前台分类页读的不是这张表本身,而是经过flat索引器扁平化之后的catalog_category_product_index表,每一次后台修改position或者添加新产品到分类,都需要触发catalog_category_product这个索引器重建。

保哥在生产环境踩过一个坑:客户后台改了position,刷新前台没反应,反复清缓存也不行。最后定位到是索引器卡在「Processing」状态没跑完,手动执行php shell/indexer.php --reindex catalog_category_product跑了一次,前台立刻同步过来。Magento 2的对应索引器名字改成cataloginventory_stockcatalog_category_product,但行为完全一致,跑php bin/magento indexer:reindex就能强制重建。

另一个容易忽视的点是position字段的默认值。新产品被分配到分类时,如果没有手动设置position,Magento会写入0或者NULL(不同版本行为略有差异)。当你改成desc排序之后,所有position=0的产品会按product_id desc再次排序,这就是为什么改完_direction = 'desc'之后,没设过position的新品能自动排前面——product_id是自增主键,新品ID永远比老品大。如果你的运营同事抱怨「为什么有些老产品也排到了前面」,去数据库查这些老产品的position字段,多半是被设成了某个比新品product_id还大的数值。

保哥推荐建一个MySQL监控视图随时查看排序状态:

SELECT
  p.entity_id AS product_id,
  v.value AS product_name,
  ccp.position,
  ccp.category_id
FROM catalog_category_product ccp
JOIN catalog_product_entity p ON ccp.product_id = p.entity_id
LEFT JOIN catalog_product_entity_varchar v
  ON v.entity_id = p.entity_id AND v.attribute_id = 71
WHERE ccp.category_id = 你的分类ID
ORDER BY ccp.position ASC, p.entity_id DESC
LIMIT 50;

这条SQL能列出指定分类下产品按当前排序规则的真实顺序,对比前台显示的顺序,如果不一致就能立刻判断是索引器没跑还是缓存没清。这是保哥定位排序问题的标准动作。

后台手动给关键产品定制position

除了改源码,Magento后台其实还提供了一个更细粒度的方式:给每个分类下的产品手动指定position数值,数字越小排得越前。保哥经手的电商站里,运营同事更喜欢这种方式,因为可以做到「新品默认排最前 + 主推产品强制置顶 + 滞销品手动后移」的精细化运营。

操作路径在后台「Catalog → Manage Categories → 选中目标分类 → 右侧面板Category Products标签页」,里面会列出当前分类下的所有产品,每行末尾有一个Position输入框:

  • Position等于1的产品排第一位
  • Position等于2的产品排第二位
  • 依此类推,数值越大越靠后

如果两个产品position相同,则按product_id大小决定先后,product_id大的(也就是更新的)排前面。这就是为什么改了_direction = 'desc'之后,对于没有手动设置position的产品,新品也能自动排前——因为它们的position字段默认是0或者NULL,于是product_id desc就接管了次级排序。

保哥的实战做法是把这两种方式组合起来:源码层把默认方向改成desc让新品自动前置,后台层给前5到10个核心爆款手动设置position 1到10,剩下的让desc排序自然处理。这样既保证了运营对核心货架的控制,也兼顾了新品自动曝光,是我经手的几十个Magento站里最稳的折中方案。

Magento 2的对应实现方式

如果你已经升级到Magento 2,那么前面那一套改Toolbar.php的做法就不适用了。Magento 2重写了整个排序架构,推荐的扩展方式是写一个独立的Module去plugin进Magento\Catalog\Block\Product\ProductList\Toolbar类,而不是直接改文件。下面是保哥的Magento 2实战版:

<?xml version="1.0"?>
<!-- app/code/Patpat/CatalogSort/etc/di.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Catalog\Block\Product\ProductList\Toolbar">
        <plugin name="patpat_catalog_sort_toolbar"
                type="Patpat\CatalogSort\Plugin\ToolbarPlugin"
                sortOrder="10"/>
    </type>
</config>
<?php
// app/code/Patpat/CatalogSort/Plugin/ToolbarPlugin.php
namespace Patpat\CatalogSort\Plugin;

class ToolbarPlugin
{
    public function afterGetCurrentDirection($subject, $result)
    {
        // 强制返回desc让新品默认排前
        return 'desc';
    }

    public function afterGetCurrentOrder($subject, $result)
    {
        // 配合position字段排序,让数值大的排前面
        return $result;
    }
}

注册模块的两个文件:

<?php
// app/code/Patpat/CatalogSort/registration.php
\Magento\Framework\Component\ComponentRegistrar::register(
    \Magento\Framework\Component\ComponentRegistrar::MODULE,
    'Patpat_CatalogSort',
    __DIR__
);
<?xml version="1.0"?>
<!-- app/code/Patpat/CatalogSort/etc/module.xml -->
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
    <module name="Patpat_CatalogSort" setup_version="1.0.0"/>
</config>

安装命令:

php bin/magento module:enable Patpat_CatalogSort
php bin/magento setup:upgrade
php bin/magento setup:di:compile
php bin/magento cache:flush

保哥在Magento 2.4.x上测试过这套写法,效果和Magento 1.x改源码完全一致,但因为是独立模块,可以一键启用关闭,跨版本升级也完全不受影响。如果你不想自己写模块,也可以直接花点钱在Marketplace上买现成的Sort by Newest类扩展,原理都是plugin进同一个Toolbar类。

3个真实电商站点排序改造案例对照

保哥这一节把过去三年经手过的三个有代表性的Magento排序改造项目拿出来对比,给读者一个真实落地参考。

案例一是一家做户外用品的Magento 1.9独立站。客户分类页有大约2000个SKU,每周上新50到80个产品,运营同事每次上新都要手工去后台把新品position设成1是不可接受的。保哥的方案是只改了一个local版Toolbar.php,desc一行改动,加上把所有老产品的position批量UPDATE成0让新品自然排前。改完之后新品上架立刻出现在分类页首屏,分类页跳出率从58%降到41%,产品详情页点击率提升19%。整个改造工时不到4小时。

案例二是一家做家居饰品的Magento 2.4站点,月销售额6位数美元。运营要求是「新品排最前但有3个主推爆款必须永远置顶」。保哥用plugin方式重写了afterGetCurrentDirection返回desc,同时给三个爆款的position手动设成1/2/3。但上线第二天就遇到问题:买了Magento官方Sort by Position扩展的网站,扩展和plugin同时生效导致排序混乱。解决办法是删掉那个第三方扩展,统一用自己的plugin管理排序方向。事后总结教训:上Magento 2的plugin之前必须先php bin/magento module:status检查是否有冲突模块。

案例三是一家做B2B工业品的Magento 1.7老站,3万SKU。这家站点的特殊性在于产品同时分布在多个分类,每个分类的排序规则都不一样:有的按价格、有的按字母、有的按上架时间。保哥的方案是不动Toolbar.php默认值,改成在每个分类的后台Default Product Listing Sort By里单独配置,这样不同分类各取所需。改造完之后客户运营效率提升明显,因为不需要每次改全局参数就能针对单个分类做精细化排序。

三个案例的共同点是:源码改动都走local覆盖路径,没有一次直接动core;改完都跑了完整的reindex + cache flush + 无痕窗口验证流程;都用git做了版本管理,方便未来回滚。这三条铁律是保哥处理Magento排序问题的红线。

验证排序生效与缓存清理

改完之后保哥的标准验证流程分四步走,每一步都不能跳。

第一步是清缓存。Magento缓存层级非常多,我习惯一次性全清,确保不会有残留。Magento 1.x直接删var/cache/*var/full_page_cache/*,Magento 2用php bin/magento cache:flush一行命令搞定。

第二步是清索引。Magento的产品列表是从索引表读取的,改了排序方向后建议重新跑一次索引:

# Magento 1.x
php shell/indexer.php --reindexall

# Magento 2
php bin/magento indexer:reindex

第三步是浏览器无痕窗口访问分类页。保哥反复强调要用无痕窗口,因为正常窗口里浏览器和Varnish/CDN都可能缓存了老的页面,让你以为改动没生效。无痕窗口加上?cb=随机数这种破坏缓存的查询参数最稳。

第四步是看产品ID顺序是否符合预期。最简单的验证方式是把鼠标悬停在产品图上看图片文件名(通常带产品SKU或ID),或者打开浏览器开发者工具看DOM里data-product-id这种属性。如果分类页前几个产品确实是ID较大的新品,就说明改动生效了。

保哥再给个进阶技巧:在测试环境里临时建一个新产品,把它分配到目标分类,记下产品ID。然后访问分类页,理论上这个新建产品应该立刻出现在第一位(前提是没有其它产品手动设置了position 1)。这个测试比看老产品列表更直观,能在三十秒内确认排序方向是否正确。

排序与价格筛选库存筛选的兼容陷阱

这是保哥经手的高阶踩坑,单独拎出来讲。Magento分类页的左侧导航通常有「按价格筛选」「按属性筛选」「按品牌筛选」等组件,这些组件会动态拼SQL查询条件追加到产品列表查询里。问题是:当你点了价格筛选之后,分类页排序方向有时候会被这些筛选模块的内部逻辑覆盖掉,导致desc失效。

保哥在一个客户站点上花了快两天才定位到根因:Magento社区里某个流行的Solr搜索扩展在筛选阶段强制把orderBy重写成position asc,覆盖了Toolbar.php的desc设置。解决办法分两条路:要么删掉这个Solr扩展回到默认搜索,要么去Solr扩展的源码里再写一层plugin把orderBy强制改回desc。最终我选了后者,因为客户分类页有50万产品离不开Solr的查询性能。

另一个相关陷阱是库存筛选。Magento开启「Hide out of stock」选项之后,列表查询会追加一个stock_status = 1条件,但部分版本的Magento会在这个条件后面隐式加上ORDER BY entity_id ASC,把你的desc覆盖了。修复办法是在Toolbar.php或者plugin里显式调用setOrder('entity_id', 'DESC')强制覆盖。保哥每次上排序改造,都会专门测试一遍开/关库存筛选两种状态下的排序是否一致。

价格区间筛选还有一个隐藏陷阱:当用户选了「100到500元」这种价格区间之后,分类页会把当前页面的product collection重置一次,部分Magento版本会丢失toolbar的direction参数。处理方式是给collection的getProductCollection方法再写一个plugin,把direction显式注入。这种坑保哥写过两次专门的blog post记录,因为反复出现。

常见问题解答

改完Toolbar.php后部分分类生效部分不生效是为什么

保哥见过最多的原因是分类层级覆盖。Magento允许在分类详情页给每个分类单独指定默认排序字段(Default Product Listing Sort By),如果某个分类后台勾选了Use Config Settings之外的具体字段(比如Name或Price),那么这个分类的排序优先级会高于Toolbar默认值。解决办法是在后台逐个分类把Default Product Listing Sort By改回Position并勾选Use Config Settings,然后清缓存重新验证。这种局部覆盖全局的设计很容易让人困惑,第一次遇到的时候保哥也是花了大半个小时才定位到。另一个常见原因是子分类没有继承父分类的设置,需要逐级检查。

源码改完之后Magento升级会不会出问题

如果你按本文第三节走local覆盖路线,升级几乎不会有问题,因为core目录里的原始Toolbar.php没动。如果你直接改了core文件,那升级前必须把改动手工记录下来,升级后再重新应用一次,否则升级流程会把你的改动覆盖回asc。保哥的强烈建议是现在就把改动迁移到local,未来每次升级前用git diff检查local目录下还有哪些自定义文件,做到心中有数。Magento 2走plugin模块路线则完全不受升级影响,是最稳的方案。

能不能在不改源码的情况下实现新品前置

可以,但需要妥协。最简单的方式是去后台System → Configuration → Catalog → Frontend找到Product Listing Sort by选项,把它从默认的Position改成Created At之类的时间字段(不同Magento版本字段名略有差异),方向改成desc。但这种方式有两个缺点:第一,前台分类页右上角的排序下拉框默认就显示Created At而不是Position,运营同事不一定喜欢;第二,不是所有Magento版本都内置Created At这个排序选项,老版本可能要自己加。综合下来保哥认为还是改Toolbar.php或者写plugin更干净。

分类页排序和搜索结果页排序是同一套逻辑吗

不完全是。Magento的搜索结果页用的是另一个block类Mage_CatalogSearch_Block_Result,排序逻辑独立于分类页Toolbar。如果你希望搜索页也按新品优先排序,需要再改一次对应的搜索Toolbar类,或者在Magento 2里再写一个plugin进搜索Toolbar。两边的代码改动几乎是镜像关系,保哥的做法是把改动统一封装成一个helper,然后两个Toolbar都调用同一个helper取得方向值,避免后期维护时漏掉一边。

position字段批量修改有没有快捷方法

有。直接通过MySQL UPDATE一条SQL批量改是最快的,比如把某分类所有产品position统一重置成0:UPDATE catalog_category_product SET position = 0 WHERE category_id = 你的分类ID。改完一定要reindex,否则前台不同步。如果是要把所有分类下某个产品强制置顶,UPDATE catalog_category_product SET position = 1 WHERE product_id = SKU对应的ID。保哥不建议在生产环境直接执行UPDATE,标准流程是先在测试库跑一遍验证结果,再用导出导入的方式同步到生产,避免误操作。

Magento 2的plugin写法和Magento 1的local覆盖哪个更推荐

看版本。如果你的站点是Magento 1.x,local覆盖是性价比最高的方案;如果是Magento 2.x,plugin是官方推荐的标准做法,比local覆盖更可控、更易调试。Magento 2其实也保留了preference机制可以做类似local覆盖的事情,但官方文档明确说plugin优先级高于preference,所以保哥的最佳实践是Magento 2环境一律走plugin路线,永远不要用preference去做行为改造。

写在最后

保哥的看法是:Magento的默认排序逻辑反映的是十几年前传统零售业的思维,但现代电商运营早就不是那个时代了,新品前置基本上是行业共识。本文给的几种方案各有适用场景:改core文件最快但有升级风险,local覆盖兼顾速度与可维护性,Magento 2 plugin是面向未来的标准做法,后台手工position是面向运营的精细化补充。保哥的建议是源码改默认方向加上后台position微调组合使用,既能让新品自动获得首屏曝光,也保留了对核心爆款的强制置顶能力,这是我经手的几十个Magento商城里最经得起时间考验的折中方案。最后再唠叨一句:所有源码改动都要做版本控制,用git把local目录纳入仓库,每一次改动都留下commit message,未来不管是排查问题还是迁移服务器,这些记录都是救命稻草。运维这种事永远是细节决定成败,今天少花的五分钟记录,明天可能就是几个小时的排查代价。

分享到
标签
版权声明

本文标题:《Magento分类页新品置顶:3种方法实战与排序改造》

本文链接:https://zhangwenbao.com/magento-sets-category-list-page-newly-released-product-forefront.html

版权声明:本文原创,转载请注明出处和链接。许可协议: CC BY-NC-SA 4.0

继续阅读
发表评论
分享到微信 或在下方手动填写
支持 Ctrl + Enter 提交