初探dedecms的搜索原理(二)

dedecms

接上文,arc.searchview.class.php类

从开头看,dede的类已经很陈旧了

    var $dsql;
    var $dtp;
    var $dtp2;
    var $TypeID;
    var $TypeLink;
    var $PageNo;
    var $TotalPage;
    var $TotalResult;
    var $PageSize;
    var $ChannelType;
    var $TempInfos;
    var $Fields;
    var $PartView;
    var $StartTime;
    var $Keywords;
    var $OrderBy;
    var $SearchType;
    var $mid;
    var $KType;
    var $Keyword;
    var $SearchMax;
    var $SearchMaxRc;
    var $SearchTime;
    var $AddSql;
    var $RsFields;
    var $Sphinx;


var已经被弃用了,php4时代的东西



我们看看这个类参数传递

$sp = new SearchView($typeid,$keyword,$orderby,$channeltype,$searchtype,$starttime,$pagesize,$kwtype,$mid);

这个是类的构造定义

function __construct($typeid,$keyword,$orderby,$achanneltype="all",$searchtype='',$starttime=0,$upagesize=20,$kwtype=1,$mid=0)


往下看

global $cfg_search_max,$cfg_search_maxrc,$cfg_search_time,$cfg_sphinx_article;
        if(empty($upagesize))
        {
            $upagesize = 10;
        }

global调用的是/data/config.cache.inc.php,对应的值分别为

$cfg_search_max = 50000;
$cfg_search_maxrc = 300;
$cfg_search_time = 3;
$cfg_sphinx_article = 'N';

并初始化每页显示最大数为10

继续往下看

        $this->TypeID = $typeid;
        $this->Keyword = $keyword;
        $this->OrderBy = $orderby;
        $this->KType = $kwtype;
        $this->PageSize = (int)$upagesize;
        $this->StartTime = $starttime;
        $this->ChannelType = $achanneltype;
        $this->SearchMax = $cfg_search_max;
        $this->SearchMaxRc = $cfg_search_maxrc;
        $this->SearchTime = $cfg_search_time;
        $this->mid = $mid;
        $this->RsFields = '';
        $this->SearchType = $searchtype=='' ? 'titlekeyword' : $searchtype;
        $this->dsql = $GLOBALS['dsql'];
        $this->dtp = new DedeTagParse();
        $this->dtp->SetRefObj($this);
        $this->dtp->SetNameSpace("dede","{","}");
        $this->dtp2 = new DedeTagParse();
        $this->dtp2->SetNameSpace("field","[","]");
        $this->TypeLink = new TypeLink($typeid);


前几行没什么好说的,类参数的传值。而$this->dsqp=$GLOBALS['dsql'],可见dede也是有全局变量的,打印一下,$GLOBALS五百来行。

大致都是一些配置信息。


        $this->dtp = new DedeTagParse();
        $this->dtp->SetRefObj($this);
        $this->dtp->SetNameSpace("dede","{","}");
        $this->dtp2 = new DedeTagParse();
        $this->dtp2->SetNameSpace("field","[","]");


构造类,并声明了两个命名空间


接下来

 // 通过分词获取关键词
        $this->Keywords = $this->GetKeywords($keyword);

这个是值得好好的研究一下的,这是dede的分词。


实际测试

$keyword = 'PHP的命名空间(namespace)是php5.3之后才有的。这个概念在C#中已经很早就有了,php中的namespace其实和c#的概念是一样的。';
echo $this->Keywords = $this->GetKeywords($keyword);

得到的结果为  :PHP的命名空间(namespace)是 namespace 命名 空间

可见它是把字符串截取,然后再重新分词,经过多次测试,观察到它的分词还算理想。可见其后台的关键词自动获取也是通过些原理弄出来的。

看一下其分词方法

 function GetKeywords($keyword)
    {
        global $cfg_soft_lang;
        $keyword = cn_substr($keyword, 50);
        $row = $this->dsql->GetOne("SELECT spwords FROM `#@__search_keywords` WHERE keyword='".addslashes($keyword)."'; ");
        if(!is_array($row))
        {
            if(strlen($keyword)>7)
            {
                $sp = new SplitWord($cfg_soft_lang, $cfg_soft_lang);
                $sp->SetSource($keyword, $cfg_soft_lang, $cfg_soft_lang);
                $sp->SetResultType(2);
                $sp->StartAnalysis(TRUE);
                $keywords = $sp->GetFinallyResult();
                $idx_keywords = $sp->GetFinallyIndex();
                ksort($idx_keywords);
                $keywords = $keyword.' ';
                foreach ($idx_keywords as $key => $value) {
                    if (strlen($key) <= 3) {
                        continue;
                    }
                    $keywords .= ' '.$key;
                }
                $keywords = preg_replace("/[ ]{1,}/", " ", $keywords);
                //var_dump($idx_keywords);exit();
                unset($sp);
            }
            else
            {
                $keywords = $keyword;
            }
            $inquery = "INSERT INTO `#@__search_keywords`(`keyword`,`spwords`,`count`,`result`,`lasttime`)
          VALUES ('".addslashes($keyword)."', '".addslashes($keywords)."', '1', '0', '".time()."'); ";
            $this->dsql->ExecuteNoneQuery($inquery);
        }
        else
        {
            $this->dsql->ExecuteNoneQuery("UPDATE `#@__search_keywords` SET count=count+1,lasttime='".time()."' WHERE keyword='".addslashes($keyword)."'; ");
            $keywords = $row['spwords'];
        }
        return $keywords;
    }

开头是定义字符编码,然后截取字符串.

接着通过关键词查询数据库中的search_keywords表中有无数据,$keyword进行了简单的过滤处理。

然后,如果keyword字符长度大于7,注utf8中一个中文3长度,也就是说超过两个中文,进行分词处理。此时调用了分词类,SplitWord,此类早已经开源放出,网上许多人在用。

                $sp = new SplitWord($cfg_soft_lang, $cfg_soft_lang);
                $sp->SetSource($keyword, $cfg_soft_lang, $cfg_soft_lang);
                $sp->SetResultType(2); //1 为全部, 2去除特殊符号
                $sp->StartAnalysis(TRUE); //是否对结果进行优化
                $idx_keywords = $sp->GetFinallyIndex();


得到结果后,去除单个字的情况, unset($sp);然后销毁类,这个学习了,以前用完类后没有进行过销毁处理,资源该节约的还是要节约的。

接着把关键词,和分后的词入库.(该关键词不存在,就插入入库,存在的话,搜索量加1入库)

这个设计还是很赞的,这个表search_keywords中存有,搜索过的关键词(唯一索引),分词处理过后的数据,查询量,以及查询结果数。

但是我发现,如果随便一个词,不管能不能搜到结果,它都是入库的。我认为最好是查询到数据以后才能入库,这样的话比较合理一些。


接下来

  if ($cfg_sphinx_article == 'Y')
        {
            // 初始化sphinx
            $this->sphinx = new SphinxClient;
            
            $mode = SPH_MATCH_EXTENDED2;            //匹配模式
            $ranker = SPH_RANK_PROXIMITY_BM25; //统计相关度计算模式,仅使用BM25评分计算
            $this->sphinx->SetServer($GLOBALS['cfg_sphinx_host'], $GLOBALS['cfg_sphinx_port']);
            $this->sphinx->SetArrayResult(true);
            $this->sphinx->SetMatchMode($mode);
            $this->sphinx->SetRankingMode($ranker);
            
            $this->CountRecordSphinx();
        } else {
        
            $this->CountRecord();
        }

它在这里是有关于sphinx的调用的,不过默认$cfg_sphinx_article是为N的,而且sphinx是要工作里在于安装和配置,已经自身索引的搭建,所以这个并没有多大的作用的,所以技术虽然是好技术,但是有多少人有那个能力,服务器有这个条件能搭起来sphinx环境呢,所以只能走$this->CountRecord()了,我们来看看此方法

function CountRecord()
    {
        $this->TotalResult = -1;
        if(isset($GLOBALS['TotalResult']))
        {
            $this->TotalResult = $GLOBALS['TotalResult'];
            $this->TotalResult = is_numeric($this->TotalResult)? $this->TotalResult : "";
        }
        if(isset($GLOBALS['PageNo']))
        {
            $this->PageNo = intval($GLOBALS['PageNo']);
        }
        else
        {
            $this->PageNo = 1;
        }
        $ksql = $this->GetKeywordSql();
        $ksqls = array();
        if($this->StartTime > 0)
        {
            $ksqls[] = " arc.senddate>'".$this->StartTime."' ";
        }
        if($this->TypeID > 0)
        {
            $ksqls[] = " typeid IN (".GetSonIds($this->TypeID).") ";
        }
        if($this->ChannelType > 0)
        {
            $ksqls[] = " arc.channel='".$this->ChannelType."'";
        }
        if($this->mid > 0)
        {
            $ksqls[] = " arc.mid = '".$this->mid."'";
        }
        $ksqls[] = " arc.arcrank > -1 ";
        $this->AddSql = ($ksql=='' ? join(' AND ',$ksqls) : join(' AND ',$ksqls)." AND ($ksql)" );
        if($this->ChannelType < 0 || $this->ChannelTypeid< 0){
            if($this->ChannelType=="0") $id=$this->ChannelTypeid;
            else $id=$this->ChannelType;
            $row = $this->dsql->GetOne("SELECT addtable FROM `#@__channeltype` WHERE id=$id");
            $addtable = trim($row['addtable']);
            $this->AddTable=$addtable;
        }else{
            $this->AddTable="#@__archives";
        }
        $cquery = "SELECT * FROM `{$this->AddTable}` arc WHERE ".$this->AddSql;
        //var_dump($cquery);
        $hascode = md5($cquery);
        $row = $this->dsql->GetOne("SELECT * FROM `#@__arccache` WHERE `md5hash`='".$hascode."' ");
        $uptime = time();

        if(is_array($row) && time()-$row['uptime'] < 3600 * 24)
        {
            $aids = explode(',', $row['cachedata']);
            $this->TotalResult = count($aids)-1;
            $this->RsFields = $row['cachedata'];
        }
        else
        {
            if($this->TotalResult==-1)
            {

                $this->dsql->SetQuery($cquery);
                $this->dsql->execute();
                $aidarr = array();
                $aidarr[] = 0;
                while($row = $this->dsql->getarray())
                {
                    if($this->ChannelType< 0 ||$this->ChannelTypeid< 0) $aidarr[] = $row['aid'];
                    else $aidarr[] = $row['id'];
                }
                $nums = count($aidarr)-1;
                $aids = implode(',', $aidarr);
                $delete = "DELETE FROM `#@__arccache` WHERE uptime<".(time() - 3600 * 24);
                $this->dsql->SetQuery($delete);
                $this->dsql->executenonequery();
                $insert = "INSERT INTO `#@__arccache` (`md5hash`, `uptime`, `cachedata`)
                 VALUES('$hascode', '$uptime', '$aids')";
                $this->dsql->SetQuery($insert);
                $this->dsql->executenonequery();
                $this->TotalResult = $nums;
            }
        }
    }

前几行,isset($GLOBALS['TotalResult'],isset($GLOBALS['PageNo']没有找到在哪定义的,所以说$this->TotalResult只能为-1了, $this->PageNo也只能为1了...以后找到了,在补吧


看这一句

$ksql = $this->GetKeywordSql();具体方法

 function GetKeywordSql()
    {
        $ks = explode(' ',$this->Keywords);
        $kwsql = '';
        $kwsqls = array();
        foreach($ks as $k)
        {
            $k = trim($k);
            if(strlen($k)<1)
            {
                continue;
            }
            if(ord($k[0])>0x80 && strlen($k)<2)
            {
                continue;
            }
            $k = addslashes($k);
            if($this->ChannelType < 0 || $this->ChannelTypeid < 0){
                $kwsqls[] = " arc.title LIKE '%$k%' ";
            }else{
                if($this->SearchType=="title"){
                    $kwsqls[] = " arc.title LIKE '%$k%' ";
                }else{
                    $kwsqls[] = " CONCAT(arc.title,' ',arc.writer,' ',arc.keywords) LIKE '%$k%' ";
                }
            }
        }
        if(!isset($kwsqls[0]))
        {
            return '';
        }
        else
        {
            if($this->KType==1)
            {
                $kwsql = join(' OR ',$kwsqls);
            }
            else
            {
                $kwsql = join(' And ',$kwsqls);
            }
            return $kwsql;
        }
    }


这个方法,值的说的

   if(ord($k[0])>0x80 && strlen($k)<2)
            {
                continue;
            }


在前面的话,已经有很多条件限制分词以后的字数了,这个判断感觉不是很有意义阿。还有这个:

 if($this->KType==1)
            {
                $kwsql = join(' OR ',$kwsqls);
            }
            else
            {
                $kwsql = join(' And ',$kwsqls);
            }

我现在不是很理解,刚开始构造函数的时候,$typeid根本就是为0的,程序走到这里,肯定执行的是else,也就是说是用and连接的,这样的话,问题就来了,如果我搜索后,分成两个词加上原keywords,最后where条件应该类似于这样的样式:arc.title LIKE '%xxxxx%'  And  arc.title LIKE '%xxx%'  And  arc.title LIKE '%xx%',这样模糊查询还有什么意义?这个条件理论上终止于arc.title LIKE '%xxxxx%',只要他满足,下面的必定满足,它不满足,也就查询不到,真是让人费解。所以说我认为构造的时候,一定要让$typeid为1,这样的话,查询才有意义。


接下来,就没什么好说的了,无法是构造完整的sql语句,查询出结果,然后把sql语句md5加密,该更新的更新,更入库的入库,至此dedecms查询机制初步研究完成。

2014-09-29 10:44:44

php
php

这是介绍的地方

本文相关标签

推荐应用

友情链接


皖ICP备14007051号-2 关于穆子龙