Magento搜索插件 – 用自带的Zend_Search_Lucene搜索引擎优化magento搜索

如你有这样的需求,就已经知道magento社区版的搜索是多么的不靠谱。这是一个人为地产品缺陷【反正官方不打算改善】

需求

我的需求很简单:
搜索结果准确一点 – 关键词匹配的越多就靠前
热卖的产品排前点 – 相同关键词匹配的情况下 销量大的靠前
需求可以无限…… 这里就先实现下这2个。
本文用 Zend Framework 自带的Zend_Search_Lucene 全文索引来构建magento搜索。

网上改善magento搜索插件大部分用 sphinx来搭建,性能也更好。用Zend_Search_Lucene是为了方便。Zend Framework 自带,无需安装其他的东西就能运行。

开发

用Zend_Search_Lucene,首先得建立索引。索引的内容可以是 标题 关键词 描述 ,任何的产品属性都可以索引。

这里只索引下面几个

  • 标题, 用于搜索
  • SKU, 用于搜索
  • 产品ID, 用于更新 删除索引
  • 销量, 用于排序

建立索引

public function runIndex(){

    $stores = Mage::getModel('core/store')->getCollection();
    //每个store单独索引
    foreach($stores as $store){
        $store_id = $store->getId();

        $index = new Zend_Search_Lucene($this->getIndexPath().$store_id, true);  //每个store单独保存索引
        Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());  

        $orderQtyArr = $this->getOrderQty();

        $collection = $this->getProductCollection($store_id);
        foreach($collection as $product){

            $contents = strtolower($product->getName());
            $contents .= '|'. strtolower($product->getSku());

            $orderQty = isset($orderQtyArr[$product_id])? $orderQtyArr[$product_id] : 0;

            $doc = new Zend_Search_Lucene_Document();  
            $doc->addField(Zend_Search_Lucene_Field::UnIndexed('orderqty', $orderQty));  //销售数量,用于排序
            $doc->addField(Zend_Search_Lucene_Field::Text('pid', $product->getId()));  
            $doc->addField(Zend_Search_Lucene_Field::Text('contents', $contents ,'UTF-8'));  

            $index->addDocument($doc);  
            echo '.';

        }
        $index->optimize(); 
        $index->commit();  

    }


}

索引后的文件
索引后的文件
索引的程序 可以单独建立一个shell脚本,方便 管理。

这里用事件来处理索引问题
after_reindex_process_catalogsearch_fulltext事件 用来在 更新默认的catalogsearch_fulltex索引后 也同时更新 本插件的索引
catalog_product_save_after事件 用来在 更新产品后更新对应产品的索引
catalog_product_delete_after_done 事件 用来清除已删除产品的索引

上面3个事件 对应的方法,不懂实现magento事件的自行补脑。

class Rongliang_SearchX_Model_Observer
{

    public function luceneFulltext(){
        Mage::helper("searchx")->runIndex();
    }

    public function updateLuceneFulltext(Varien_Event_Observer $observer){

        $product = $observer->getEvent()->getDataObject();
        Mage::helper("searchx")->updateIndex($product);
    }

    public function deleteLuceneFulltext(Varien_Event_Observer $observer){

        $product = $observer->getEvent()->getProduct();
        Mage::helper("searchx")->deleteIndex($product);

    }

}

更新索引

Zend_Search_Lucene没有更新的方法,其实就是先删除再添加,删除的时候搜索产品ID ,然后删掉。

    //更新索引
    public function updateIndex($product){

        $index = new Zend_Search_Lucene($this->getIndexPath().$product->getStoreId());  
        Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());  
        $query = Zend_Search_Lucene_Search_QueryParser::parse($product->getId(), "UTF-8");  
        $index->setDefaultSearchField('pid');
        $hits = $index->find($query);  
        foreach ($hits as $hit){  
            $index->delete($hit->id);  
            Mage::log($hit->id,null,'hit.log');
        }  

        $contents = strtolower($product->getName());
        $contents .= '|'. strtolower($product->getSku());

        $orderQty = $this->getOrderQtyById($product->getId());

        $doc = new Zend_Search_Lucene_Document();  
        $doc->addField(Zend_Search_Lucene_Field::UnIndexed('orderqty', $orderQty));  //销售数量,用于排序
        $doc->addField(Zend_Search_Lucene_Field::Text('pid', $product->getId()));  
        $doc->addField(Zend_Search_Lucene_Field::Text('contents', $contents ,'UTF-8'));  

        $index->addDocument($doc);  

        $index->optimize();  
        $index->commit(); 

    }

接管Magento搜索

重写Mage_CatalogSearch_Model_Resource_Fulltext的方法prepareResult($object, $queryText, $query)
把 从Zend_Search_Lucene 的搜索结果 合成数组 $this->_foundData[产品ID] = 排序 返回

public function prepareResult($object, $queryText, $query)
{
    try{

        $index = new Zend_Search_Lucene(Mage::helper("searchx")->getIndexPath().Mage::app()->getStore()->getId());
        $keyword =strtolower($queryText);
        $this->_foundData = array();

        Zend_Search_Lucene_Analysis_Analyzer::setDefault(new Zend_Search_Lucene_Analysis_Analyzer_Common_Utf8Num_CaseInsensitive());
        $userQuery = Zend_Search_Lucene_Search_QueryParser::parse($keyword, "UTF-8");
        $index->setDefaultSearchField('contents');
        //$index->setResultSetLimit(Mage::helper("searchx")->getLimit()); //设置后结果不准确

        $hits = $index->find($userQuery,'score',SORT_DESC,'orderqty',SORT_DESC);

        $i = count($hits);
        $j = 0;
        foreach ($hits as $hit) {
            $this->_foundData[$hit->pid] = $i--;

            $j++;
            if($j>Mage::helper("searchx")->getLimit()){
                break;
            }
        }

    }
    catch(Exception $e){
        //出错,返回默认的搜索
        return parent::prepareResult($object, $queryText, $query);
    }

    return $this;
}

结语

本插件搜索排序可以根据 添加时间 价格 等等,开发应用还是蛮方便的。
Zend_Search_Lucene 的搜索方式请查看官方文档,实现更多需求。这里只有了最简单的一个。
性能没测,也没对比过, 我这边的都是小站,性能没压力。想练手可以 把sphinx换进去,
这里只测试了 magento 1.9.3.1 以上版本,最近几个版本的 CatalogSearch 模块变动很大,请注意

写的匆忙,待完善完善。重构下代码把插件发布出来。

升级到了Magento 1.9.3.2 及碰到的一些问题

Magento 1.9.3.2发布也有好几天了,最近有点忙没第一时间升级。今天下午开始了升级,和往常一样,以为是个很简单的事情。screen 里面就直接运行了升级命令,服务器在国内,下载升级的数据很慢很慢

晚上打开网站看了下,竟然报404错误。。这不科学啊,再怎么升级失败都不至于是这个错误的。登录服务器进去看看,magento原始的程序相关文件竟然被删光了。一直没碰到过这情况。screen 里面看了下,确实是升级出问题了。
具体文件是php版本不符合要求,我用了php7,而显示的错误是magento1模块要求是5.2<=php<=6。没想到是先删除文件再检查php环境,真有点汗了。

没办法,只能去官网下载1.9.3.2程序包再覆盖上传进行升级了。下载那是坎坷啊,挂了VPN,magento2.1.4的一下子就下载完了,而magento1.9.3.2试了几十次才下载完成,真怀疑官方是不是故意的。而且部署到国外的站最近升级也明显的要多花很多时间了。

官网这么变态的下载问题,以后有新版本我还是搬运过来吧。不然就是《Magento从下载到放弃》

Magento URL中的uenc参数说明

浏览器查看magento 的html源文件,就会发现有很多如下的链接中带有uenc参数

catalog/product_compare/index/items/45/uenc/a———-od–MuY29tLw,,/

那么Magento URL中的uenc参数是干什么的?

//Mage_Core_Controller_Varien_Action 文件中
const PARAM_NAME_URL_ENCODED
    protected function _getRefererUrl()
    {
        $refererUrl = $this->getRequest()->getServer('HTTP_REFERER');
        if ($url = $this->getRequest()->getParam(self::PARAM_NAME_REFERER_URL)) {
            $refererUrl = $url;
        }
        if ($url = $this->getRequest()->getParam(self::PARAM_NAME_BASE64_URL)) {
            $refererUrl = Mage::helper('core')->urlDecodeAndEscape($url);
        }
        if ($url = $this->getRequest()->getParam(self::PARAM_NAME_URL_ENCODED)) {
            $refererUrl = Mage::helper('core')->urlDecodeAndEscape($url);
        }

        if (!$this->_isUrlInternal($refererUrl)) {
            $refererUrl = Mage::app()->getStore()->getBaseUrl();
        }
        return $refererUrl;
    }

进入这个文件 Mage_Core_Helper_Abstract 不难看出这个uenc参数保存的是当前页面的url的base64值,作用是进行某些操作后能 返回uenc对应的链接。

Magento2自定义路由实现URL重写

最近在写个magento2的blog插件,需要实现URL伪静态。
更多可以参考magento2自带的CMS 模块
代码如下:

先定义di文件,app/code/Mageoo/MyBlog/etc/frontend/di.xml

<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <type name="Magento\Framework\App\RouterList">
        <arguments>
            <argument name="routerList" xsi:type="array">
                <item name="cms" xsi:type="array">
                    <item name="class" xsi:type="string">Mageoo\MyBlog\Controller\Router</item>
                    <item name="disable" xsi:type="boolean">false</item>
                    <item name="sortOrder" xsi:type="string">70</item>
                </item>
            </argument>
        </arguments>
    </type>
</config>

di.xml配置对应的Router class文件 app/code/Mageoo/MyBlog/Controller/Router.php

    public function match(\Magento\Framework\App\RequestInterface $request)
    {
        $identifier = trim($request->getPathInfo(), '/');

        $condition = new \Magento\Framework\DataObject(['identifier' => $identifier, 'continue' => true]);
        $this->_eventManager->dispatch(
            'cms_controller_router_match_before',
            ['router' => $this, 'condition' => $condition]
        );
        $identifier = $condition->getIdentifier();

        if ($condition->getRedirectUrl()) {
            $this->_response->setRedirect($condition->getRedirectUrl());
            $request->setDispatched(true);
            return $this->actionFactory->create('Magento\Framework\App\Action\Redirect');
        }

        if (!$condition->getContinue()) {
            return null;
        }

        //identifier 获取到了identifier 后,就可以查找具体blogid,然后把参数分发到具体的Action处理。

        $request->setModuleName('myblog')->setControllerName('index')->setActionName('index')->setParam('blogid', $blogid);
        $request->setAlias(\Magento\Framework\Url::REWRITE_REQUEST_PATH_ALIAS, $identifier);

        return $this->actionFactory->create('Magento\Framework\App\Action\Forward');
    }

使用Varnish提高Magento访问速度

使用Varnish提高Magento访问速度 ,Varnish乃magento的神器,让无数泪蹦的娃露出灿烂的微笑。这里整理下Varnish的安装使用方法和常碰到的问题,欢迎拍砖。

Varnish 安装

已CentOS为例

具体参考

http://varnish-cache.org/releases/install_redhat.html#install-redhat

CentOS 6.X

yum install epel-release
rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-4.0.el6.rpm
yum install varnish

CentOS 7

yum install epel-release
rpm --nosignature -i https://repo.varnish-cache.org/redhat/varnish-4.0.el7.rpm
yum install varnish

安装完成后,启动varnish

service varnish start
service varnish restart
service varnish stop

Varnish配置

后端web服务器配置

Varnish VCL配置

手机端与PC端不是同一个模板

不缓存的URL设置

https 与 http 分开缓存

Magento Nexcessnet_Turpentine 插件设置

没完成!

Magento2 static/version 资源文件404 NOT FOUND问题

Magento2 static/version 资源文件404 NOT FOUND问题。

例如下面的链接 xxxx.com/static/version1483410588/frontend/……

version1483410588这个文件夹服务器里面压根就找不到。

这个其实也不是问题,是伪静态没配置好。

查看magento2根目录下的nginx.conf.sample文件,里面的配置就可以看出问题所在。

location /static/ {
    # Uncomment the following line in production mode
    # expires max;

# Remove signature of the static files that is used to overcome the browser cache
location ~ ^/static/version {
    rewrite ^/static/(version\d*/)?(.*)$ /static/$2 last;
}

location ~* \.(ico|jpg|jpeg|png|gif|svg|js|css|swf|eot|ttf|otf|woff|woff2)$ {
    add_header Cache-Control "public";
    add_header X-Frame-Options "SAMEORIGIN";
    expires +1y;

    if (!-f $request_filename) {
        rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
    }
}
location ~* \.(zip|gz|gzip|bz2|csv|xml)$ {
    add_header Cache-Control "no-store";
    add_header X-Frame-Options "SAMEORIGIN";
    expires    off;

    if (!-f $request_filename) {
       rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
    }
}
if (!-f $request_filename) {
    rewrite ^/static/(version\d*/)?(.*)$ /static.php?resource=$2 last;
}
add_header X-Frame-Options "SAMEORIGIN";
}

想去掉这个数字 在后台设置

Configuration/Developer/Static Files Settings/Sign Static Files/ 设置为NO

新安装的magento2后台错乱运行下面的SQL后清理缓存。

INSERT INTO `core_config_data` (`config_id`, `scope`, `scope_id`, `path`, `value`) VALUES (NULL, 'default', '0', 'dev/static/sign', '0')

好吧,这又是一篇废话文章……

记录一下,2016最后一天把本站从wordpress转到了Magento,从国外转回了国内

WordPress的确是个很不错的blog系统,只是本站的一些内容需要magento系统配合来做演示。再说了,玩得是magento,索性就转到magento来了,保留了原来wordpress的URL 结构,用Bootstrap简单做了个模板。。如你所见的样子了,后面的慢慢折腾完善。

原来网站是放在linode的日本东京机房,速度跟稳定性最近是没法接受了,已经彻底放弃了linode。一些不重要的小站放到了阿里云HK 机房,国内速度还不错。Mageoo.com要认真搞,就去备案了,广东地区的备案速度还算可以,备案很多次基本都是5工作日完成。备案期间网站没完全关闭,蜘蛛放行,广东地区的IP给屏蔽了。反正就这么过关了。

现在网站是放到阿里云深圳的机房,服务器配置是最基本了1核1G的,反正运行起来无压力。带宽太小,用了百度的CDN,按流量付费,现在网站没什么流量一个月也就几块钱的样子。

话说现在网站是  百度CDN ->Nginx(https)->Varnish->Apache  这纯属瞎折腾了,但这也是我改用magento的原因:就是折腾!

Nginx使用CloudFlare CDN加速后获取真实客户端IP

Nginx使用CloudFlare CDN加速后,如果不做处理,通过 $SERVER[“REMOTEADDR”> 获取到的IP会是 CloudFlare的服务器IP Nginx 需要启用http_realip_module目录,
编译的时候带上 –with-http_realip_module
Nginx 配置加上

set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 104.16.0.0/12;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 131.0.72.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 199.27.128.0/21;
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2c0f:f248::/32;
set_real_ip_from 2a06:98c0::/29;

# use any of the following two
#real_ip_header CF-Connecting-IP;
real_ip_header X-Forwarded-For;
real_ip_recursive on;

如果 nginx 前面还有一层varnish的话 还要加上varnish的IP

set_real_ip_from   127.0.0.1/32;#与nginx同服务器的情况
set_real_ip_from   具体varnish服务器的ip;#与nginx不同服务器的情况

php通过 $SERVER[“REMOTEADDR”> 就能获取到真实的用户IP

相关文档 :https://support.cloudflare.com/hc/en-us/articles/200170706-How-do-I-restore-original-visitor-IP-with-Nginx-

Magento的Prototype与Bootstrap冲突问题及解决

Magento引入Bootstrap的时候,会产生冲突问题,最明显的就是dropdown等控件在选择的时候会把整个控件给hide了

解决办法

修改 prototype.js 文件,最开头加入下面代码

var isBootstrapEvent = false;
if (window.jQuery) {  
  jQuery('*').on('hide.bs.dropdown', function( event ) {
    isBootstrapEvent = true;
  });
  jQuery('*').on('hide.bs.collapse', function( event ) {
    isBootstrapEvent = true;
  });
  jQuery('*').on('hide.bs.modal', function( event ) {
    isBootstrapEvent = true;
  });
}

大概1931行 修改为

    if(!isBootstrapEvent){
        element.style.display = 'none';
    }

使用Full Page Cache插件Lesti_Fpc加速magento

Magento Full Page Cache插件很多,这里介绍Lesti_Fpc这个免费全页缓存插件。

插件安装

插件下载:https://github.com/GordonLesti/Lesti_Fpc 安装完成后到Cache Management你们可以看到Fpc 这栏。 然后开启它

使用设置

Lesti_Fpc 的后台配置在:ADVANCED > System > Lesti FPC 这里主要说下Lesti_Fpc处理一些动态内容block的问题,例如显示购物车数量的block,这个是不能缓存的。 动态内容的block只需要把布局配置文件layout里面的block name 填入设置的Lazy Blocks中即可。 默认设置的那几个block是magento自带的模板里面的动态内容block。 例如里面的 minicart_head 这个是 rwd模板 右上角 显示 购物车内容的 block 其名称来自文件:/app/design/frontend/rwd/default/layout/checkout.xml

<reference name="header">
            <block type="checkout/cart_minicart" name="minicart_head" template="checkout/cart/minicart.phtml" before="-">
                <block type="checkout/cart_sidebar" name="minicart_content" template="checkout/cart/minicart/items.phtml">
                ...

看了上面的代码,应该是很容易理解了。需要用这个Full Page Cache插件的话就这样根据你的模板来配置。

缓存配置

参考app/etc/fpc.xml.sample 的文件,可以把配置直接放入local.xml文件的节点

Redis

            <backend>Cm_Cache_Backend_Redis</backend>
            <backend_options>
                <server>127.0.0.1</server>
                <port>6379</port>
                <persistent>cache-fpc</persistent>
                <database>1</database>
                <password></password>
                <force_standalone>1</force_standalone>
                <connect_retries>1</connect_retries>
                <lifetimelimit>86400</lifetimelimit>
                <read_timeout>10</read_timeout>
                <compress_data>1</compress_data>
                <compress_tags>1</compress_tags>
                <compress_data>gzip</compress_data>
            </backend_options>

注意设置persistent 的值 ,避免同一个 redis database安装了多个magento造成的冲突

apc

            <backend>apc</backend>
            <prefix>LESTI_FPC_</prefix-->

memcached

<backend>memcached</backend>
            <memcached>
                <servers>
                    <server>
                        <host><![CDATA[127.0.0.1]]></host>
                        <port><![CDATA[11211]]></port>
                        <persistent><![CDATA[1]]></persistent>
                    </server>
                </servers>
                <compression><![CDATA[0]]></compression>
                <cache_dir><![CDATA[]]></cache_dir>
                <hashed_directory_level><![CDATA[]]></hashed_directory_level>
                <hashed_directory_umask><![CDATA[]]></hashed_directory_umask>
                <file_name_prefix><![CDATA[]]></file_name_prefix>
            </memcached>