初探dedecms的搜索原理(一)
dedecms

上图为dedecms中自带的搜索界面。搜索界面很简单,由一个text,一个select,一个button,构建为一个form表单,具体代码如下:
<form action="/plus/search.php" name="formsearch"><div class="form"> <h4>搜索</h4> <input type="hidden" value="0" name="kwtype"> <input id="search-keyword" class="search-keyword" type="text" onblur="if(this.value==''){this.value='在这里搜索...';}" onfocus="if(this.value=='在这里搜索...'){this.value='';}" value="在这里搜索..." name="q"> <select id="search-option" class="search-option" name="searchtype"> <option selected="1" value="title">检索标题</option> <option value="titlekeyword">智能模糊</option> </select> <button class="search-submit" type="submit">搜索</button> </div> </form>
进入search.php文件
开头代码
require_once(dirname(__FILE__)."/../include/common.inc.php"); require_once(DEDEINC."/arc.searchview.class.php"); $pagesize = (isset($pagesize) && is_numeric($pagesize)) ? $pagesize : 10; $typeid = (isset($typeid) && is_numeric($typeid)) ? $typeid : 0; $channeltype = (isset($channeltype) && is_numeric($channeltype)) ? $channeltype : 0; $kwtype = (isset($kwtype) && is_numeric($kwtype)) ? $kwtype : 0; $mid = (isset($mid) && is_numeric($mid)) ? $mid : 0;
这些参数的过滤由common.inc.php中处理并定义的,相关代码为
CheckRequest($_REQUEST); CheckRequest($_COOKIE); foreach(Array('_GET','_POST','_COOKIE') as $_request) { foreach($$_request as $_k => $_v) { if($_k == 'nvarname') ${$_k} = $_v; else ${$_k} = _RunMagicQuotes($_v); } } }
具体怎么过滤的,暂且不表。
接下来的一些,为防止sql注入,而进行的简单的正则替换
if(!isset($orderby)) $orderby=''; else $orderby = preg_replace("#[^a-z]#i", '', $orderby); if(!isset($searchtype)) $searchtype = 'titlekeyword'; else $searchtype = preg_replace("#[^a-z]#i", '', $searchtype); if(!isset($keyword)){ if(!isset($q)) $q = ''; $keyword=$q; } $oldkeyword = $keyword = FilterSearch(stripslashes($keyword));
接下来是查找栏目信息,目前不是很了解它是做什么的,正好研究一下。代码为
//查找栏目信息 if(empty($typeid)) { $typenameCacheFile = DEDEDATA.'/cache/typename.inc'; if(!file_exists($typenameCacheFile) || filemtime($typenameCacheFile) < time()-(3600*24) ) { $fp = fopen(DEDEDATA.'/cache/typename.inc', 'w'); fwrite($fp, "<"."?php\r\n"); $dsql->SetQuery("Select id,typename,channeltype From `#@__arctype`"); $dsql->Execute(); while($row = $dsql->GetArray()) { fwrite($fp, "\$typeArr[{$row['id']}] = '{$row['typename']}';\r\n"); } fwrite($fp, '?'.'>'); fclose($fp); } //引入栏目缓存并看关键字是否有相关栏目内容 require_once($typenameCacheFile); if(isset($typeArr) && is_array($typeArr)) { foreach($typeArr as $id=>$typename) { //$keywordn = str_replace($typename, ' ', $keyword); $keywordn = $keyword; if($keyword != $keywordn) { $keyword = HtmlReplace($keywordn); $typeid = intval($id); break; } } } }
首先它把栏目缓存了,如果没有或者是过期的话,会重新生成。
然后foreach把栏目依次输出,但是下面我就不知道他有什么意义了,
$keywordn = $keyword;
$keywordn = $keyword; if($keyword != $keywordn)
这样的写法简直是滑稽,导致整个查找栏目信息没有任何意义。
接着往下看,
$keyword = addslashes(cn_substr($keyword,30)); $typeid = intval($typeid); if($cfg_notallowstr !='' && preg_match("#".$cfg_notallowstr."#i", $keyword)) { ShowMsg("你的搜索关键字中存在非法内容,被系统禁止!","-1"); exit(); } if(($keyword=='' || strlen($keyword)<2) && empty($typeid)) { ShowMsg('关键字不能小于2个字节!','-1'); exit(); }
前两行对keyword进行php自带函数addslashes简单过滤,对typeid的int化,而后出现一个$cfg_notallowstr变量,经过断点追溯,找到其是存在/data/config.cache.inc.php中的,相关代码为
$cfg_notallowstr = '非典|艾滋病|阳痿'; $cfg_replacestr = '她妈|它妈|他妈|你妈|去死|贱人'; $cfg_feedbackcheck = 'N';
这个应该是可以从后台直接操作的,暂时不表。
继续往下看代码
//检查搜索间隔时间 $lockfile = DEDEDATA.'/time.lock.inc'; $lasttime = file_get_contents($lockfile); if(!empty($lasttime) && ($lasttime + $cfg_search_time) > time()) { ShowMsg('管理员设定搜索时间间隔为'.$cfg_search_time.'秒,请稍后再试!','-1'); exit(); }
引入了一个时间锁的配置文件,其文件里存一个时间戳工具,原理是,每查询一次,向里注入最新的时间戳,我也在文件的最后找到了相关代码
PutFile($lockfile, time());
不过我对这个机制不是很理解,它这样设置,是针对整个服务器进行设置的,也就是说,如果A和B能过两台电脑,两个ip段进行在误差$cfg_search_time内进行查询的话,理论上有一个人是查询不到的。这样是避免了恶意查询,但是把用户体验我认为是做到了最差。
如果单纯的防止用户恶意查询,用cookie或者session是可行的。如果单纯是为了减轻服务器的压力,可以在想别的办法,比如说sphinx全文索引,完善的缓存机制,以及其他的技术手段实现,而不能用这么简单暴力的方法。
继续往下看
//开始时间 if(empty($starttime)) $starttime = -1; else { $starttime = (is_numeric($starttime) ? $starttime : -1); if($starttime>0) { $dayst = GetMkTime("2008-1-2 0:0:0") - GetMkTime("2008-1-1 0:0:0"); $starttime = time() - ($starttime * $dayst); } }
我现在还不知道$starttime是怎么来的,我得到它的值一直都为空,无从追溯,现在姑且认为这希它的值定义为-1吧...
继续往下看
$t1 = ExecTime();
此函数定义在/include/helpers/debug.help.php中,具体代码为
if ( ! function_exists('ExecTime')) { function ExecTime() { $time = explode(" ", microtime()); $usec = (double)$time[0]; $sec = (double)$time[1]; return $sec + $usec; } }
具体用处是把micrtome()返回的 类似于这样的格式 0.83355200 1411874405 重组为 1411874405.8336 然后返回给$t1
继续往下看代码,最后三行
$sp = new SearchView($typeid,$keyword,$orderby,$channeltype,$searchtype,$starttime,$pagesize,$kwtype,$mid); $keyword = $oldkeyword; $sp->Display();
这段代码没什么好说的,只是开始调用类了,并初始化$keyword。
下面在开一文研究这类