php调用sphinx有两种方式:
- 一是引入sphinx的接口
- 二是安装php的sphinx扩展
sphinx重要的几个方法
①setMatchMode ( int $mode )全文检索模式匹配
$mode表示匹配模式, 有以下5种匹配模式
Constant Description
SPH_MATCH_ALL 匹配所有查询关键字,比如你搜索"杭州钢材",那么实际搜索的是同时包含"杭州"和"钢材"两个关键字的文章
SPH_MATCH_ANY 匹配任意一个查询关键字,比如你搜索"杭州钢材",那么实际搜索的是包含"杭州"或"钢材"关键字的文章
SPH_MATCH_PHRASE 将查询看成一个词组,要求按顺序完整匹配,这个类似于select * like "%杭州钢材%"。比如你搜"2016年8月份杭州钢材"能搜到,但是搜2016年8月份杭州统计汇总,钢材价格"就搜不到。
SPH_MATCH_BOOLEAN 将查询看成一个布尔表达式,可以简单地进行与或运算。例如(杭州!钢材),搜索匹配杭州但是不匹配钢材的记录
SPH_MATCH_EXTENDED2 sphinx扩展匹配模式,将查询看成sphinx内部的表达式,可以使用以下运算符:
或 例如:杭州 |钢材
非 例如:杭州 !钢材
字段搜索符 例如@title 杭州 @description 钢材 表示搜索文章title包含杭州,描述包含钢材的记录<
字段限位 例如@title[100] 杭州 表示搜索标题包含杭州,并且标题长度不超过100的记录
多字段搜索符 例如@(title,content)杭州钢材 表示搜索标题或文章内容里包含"杭州钢材"的记录
严格搜索符 例如 杭州 << 钢材 表示杭州,钢材需要按顺序出现
字段开始和结束符 例如 ^杭州...钢材$ 表示限定必须以杭州开始,钢材结束
SPH_MATCH_FULLSCAN 完整扫描,此模式下所有的查询词都将被忽略,没有分词结果。
SPH_MATCH_EXTENDED 同SPH_MATCH_EXTEND2
②setSortMode 全文检索排序模式
Constant Description
SPH_SORT_RELEVANCE 按相关度评分排序,最好的匹配排在前面
SPH_SORT_ATTR_DESC 按照属性降序排列 例如 :setSortMode(SphinxClient::SPH_SORT_ATTR_DESC,'publish_time')按照发布时间降序
SPH_SORT_ATTR_ASC 按照属性升序排列 例如:setSortMode(SphinxClient::SPH_SORT_ATTR_ASC'publish_time')按照发布时间升序
SPH_SORT_TIME_SEGMENTS segments表示部分,段的意思。这个模式表示先按时间排序,再按相关度排序。这个时间是sphinx自己内部定义的规则
SPH_SORT_EXTENDED 按类似sql的方式排列组合起来,比如@weight desc,publish_time desc。表示先按权值,即匹配度降序,再按发布时间降序
其中以@开头的是内置属性,包括
@weight 权值
@id 文档id
@relevance 等同于weight
SPH_SORT_EXPR 表达式排序方式,即可以按照表达式计算出来的结果进行排序。例如 setSortMode(SphinxClient::SPH_SORT_EXPR,'@weight+@id')
③setRankingMode 搜索排名
Constant Description
SPH_RANK_PROXIMITY_BM25 Default ranking mode which uses both proximity and BM25 ranking
SPH_RANK_BM25 Statistical ranking mode which uses BM25 ranking only (similar to most of other full-text engines). This mode is faster, but may result in worse quality on queries which contain more than 1 keyword.
SPH_RANK_NONE 不进行排序
④setFilterRanging($attribute,$min,$max,$exclude=false)
指定一个值的范围,例如setFilterRanging(‘publish_time’,’2016-08-26 00:00:00′,’2016-08-27 00:00:00′);第四个参数若为true,表示取不在这个范围的数据。
⑤setFieldweights(array $weights)设置查询的权重,权重大的会优先搜索
例如:
setFieldweights([
'title'=>10,
'content'=>6,
'description'=>3
]);//优先匹配title里包含关键字的,然后是content,然后是description。
⑥setLimits($offset,$limit,$max=1000,$cutoff)分页查询
$offset,$limit类似于mysql里的offset,limit.
$max表示搜索请求中返回的最大数据量,默认是1000.比如搜索”行情”,实际上有10000条记录,但是没有设置此参数,默认只返回1000条记录。所有totals为1000.所以一般这个值须要设置。
$cutoff控制查询的数量限制,它跟$max的区别是:$max是返回的最大数据量,比如说实际搜到了10000条记录,但是只返回1000条。$cutoff表示一开始搜索时就限制搜索的数据量,比如最多搜索50条停止,那么搜索到50条就会停止搜索。这个一般可以不做设置。
⑦setGroupBy($attribute,$func,$groupsort=’@group desc ‘)分组查询
$func默认有以下几个:
SPH_GROUPBY_DAY,
SPH_GROUPBY_WEEK,
SPH_GROUPBY_MONTH,
SPH_GROUPBY_YEAR,
SPH_GROUPBY_ATTR,
SPH_GROUPBY_ATTRPAIR
DAY,WEEK,MONTH,YEAR这个一般可以用于搜索最近一天,一周,一月或一年的记录。ATTR表示按照指定的属性值分组
⑧setArrayResult设置返回的数据为数组
⑨setFilter($attribute,array $value,$exclude = false)
属性过滤,例如setFilter(‘id’,[1,2,3]);第三个参数若为true,表示id不为1,2,3的。
php里调用sphinx例子
包括搜索和更新状态两个功能使用
//实例化
require_once("sphinxclient.class.php");
$sphinx = new SphinxClient;
//搜索功能
$mode = SPH_MATCH_EXTENDED2; //匹配模式
$ranker = SPH_RANK_PROXIMITY_BM25; //统计相关度计算模式,仅使用BM25评分计算
$sphinx->SetServer('127.0.0.1', '9312');
$sphinx->SetArrayResult(true);
$sphinx->SetMatchMode($mode);
$sphinx->SetRankingMode($ranker);
//过滤status字段,只显示正常帖子,不显示已删除帖子。字段要在sphinx配置为索引
$sphinx->SetFilter('status', array(0));
//过滤时间段
if($StartTime > 0)
{
$sphinx->SetFilterRange('senddate', $StartTime, time(), false);
}
//设置字段的权重
$sphinx->SetFieldWeights(array('title' => 10, 'description' => 5, 'body' => 5));
//设置排序,先按权重,再按id
$sphinx->SetSortMode( SPH_SORT_EXTENDED, "@weight DESC, @id desc" );
//分页
$limitstart = 0;
$row = 10;
$sphinx->SetLimits($limitstart, (int)$row, ($row>1000) ? $row : 1000);
//结果
$res = array();
$res = $sphinx->Query($this->Keywords." @flag !s", 'mysql, delta');
var_dump($res);
//删除索引,删除帖子时更新状态(主索引和增量索引),不让搜索时搜索出来
$sphinx->UpdateAttributes('mysql', array('status'),array($aid => array(-2)));
$sphinx->UpdateAttributes('delta', array('status'),array($aid => array(-2)));
框架中使用
我在thinkphp5框架中使用。
php环境为7.2
后发现sphinx的高亮显示出了问题。自己中间加入了中文分词手动替换高亮显示
namespace app\admin\service;
use function array_column;
use function array_combine;
use function array_key_exists;
use function is_array;
use function join;
use think\Db;
use think\Exception;
use think\Paginator;
use think\paginator\driver\Bootstrap;
use function var_dump;
class sphinx
{
private $sphinx;
private $data=[];
private $keyword;
private $tableArr=['wk_courses'=>1,'wk_article'=>2,'wk_wenda_post'=>3];
private $total_found=0;
private $table_total=[];
private $opts=[
"before_match"=>'<span style="color: red;">',
"after_match"=>'</span>',
];
public function __construct()
{
$this->sphinx = new \SphinxClient;
$this->sphinx->setServer("localhost", 9312);
$this->sphinx->setMatchMode(SPH_MATCH_EXTENDED2);
// $this->sphinx->setMatchMode(SPH_MATCH_ANY);
$this->sphinx->setMaxQueryTime(30);
$this->sphinx->SetArrayResult(false);
$this->sphinx->SetSelect ( "*" );
$weights = array ('title'=>94, 'content'=>6);
$this->sphinx->SetFieldWeights($weights);
//设置返回信息的内容,等同于SQL
// $this->sphinx->SetSortMode(SPH_SORT_RELEVANCE);
$this->sphinx->SetSortMode( SPH_SORT_EXTENDED, "@weight DESC, @id desc" );
$this->sphinx->SetRankingMode(SPH_RANK_PROXIMITY_BM25);
return $this;
}
public function keyWord($keyword)
{
$this->keyword=trim($keyword);
return $this;
}
/**
* @param $table
* @param int $count
* @return $this
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function search($table,$count=5)
{
$this->sphinx->SetLimits ( 0, $count, 1000,0);
$res = $this->sphinx->query($this->keyword,"{$table}:{$table}_delta"); #1 关键字,*是所有数据源source
if(!array_key_exists($table,$this->tableArr)
|| $this->keyword==null
|| $res==false
||empty($res['matches'])){
return $this;
}
$total_found=$res['total_found'];
if($total_found>$count){$more=true;}else{$more=false;}
$ids=join (',',array_keys($res['matches']));
if(empty($ids)){return $this;}
// $field=Db::query("SHOW COLUMNS FROM `".$table."`",[]);
// $fieldArr=array_column($field,'Field');
$data=[];
// $res=Db::table($table)->where(['id'=>['in',$ids]])->select()->toArray();
$data=Db::table($table)->where(['id'=>['in',$ids]])->select()->toArray();
// try{
// foreach ($res as $key=>$val){
// $tmp=[];
// $tmpData=$this->sphinx->BuildExcerpts( $val, $table, $this->keyword, $this->opts);
// foreach ($tmpData as $k=>$v){
// $tmp[$fieldArr[$k]]=$v;
// }
// $tmp['db_type']=$this->tableArr[$table];
// $tmp=$this->$table($tmp,$table);
// $data[]=$tmp;
// }
// }catch (\Exception $e){
// $data=$res;
foreach ($data as $key=>$val){
$data[$key]['db_type']=$this->tableArr[$table];
}
$data=$this->$table($data,$table);
// }
$this->data[$this->tableArr[$table]]=$data;
$this->total_found+=$total_found;
$this->table_total[$this->tableArr[$table]]=$more;
return $this;
}
public function getData()
{
$data=[];
$data['num']=$this->total_found;
$data['data']=$this->data;
$data['more_status']=$this->table_total;
return $data;
}
public function SearchLimitTable($page=0,$pageSize=10,$table,$path)
{
$this->sphinx->SetLimits (($page-1)*$pageSize, $pageSize, 1000,0);
$res = $this->sphinx->query($this->keyword,"{$table}:{$table}_delta"); #1 关键字,*是所有数据源source
if(!array_key_exists($table,$this->tableArr)
|| $this->keyword==null
|| $res==false
||empty($res['matches'])){
return false;
}
$this->total_found=$res['total_found'];
$ids=join (',',array_keys($res['matches']));
if(empty($ids)){return FALSE;}
$result=Db::table($table)->where(['id'=>['in',$ids]])->select()->toArray();
$result=$this->$table($result,$table);
$data=[];
$pageConfig=['type' => 'bootstrap','var_page' =>'page','list_rows' => 15,'path'=>'/'.$path ];
$pageConfig['query']['word']=$this->keyword;
$pageObj=Bootstrap::make($result, $pageSize, $page, $res['total_found'], false, $pageConfig);
$data['data']=$result;
$data['page']=$pageObj->render();
$data['num']=$res['total_found'];
return $data;
}
public function scws(string $string)
{
$word=[];
if (config('scws.start') && extension_loaded("scws")) {
//实例化分词插件核心类
$so = scws_new();
//设置分词时所用编码
$so->set_charset('utf-8');
//设置分词所用词典(此处使用utf8的词典)
$so->set_dict('/usr/local/scws/etc/dict.utf8.xdb');
//设置分词所用规则
$so->set_rule('/usr/local/scws/etc/rules.utf8.ini');
//分词前去掉标点符号
$so->set_ignore(TRUE);
//是否复式分割,如“中国人”返回“中国+人+中国人”三个词。
$so->set_multi(TRUE);
//设定将文字自动以二字分词法聚合
$so->set_duality(TRUE);
//要进行分词的语句
$so->send_text($string);
//获取分词结果,如果提取高频词用get_tops方法
$word=[];
while ($tmp = $so->get_result()) {
foreach ($tmp as $k=>$v){
array_push($word,$v['word']);
}
}
$so->close();
}
if(empty($word)){
$word=[$string];
}
return $word;
}
/**
* 课程索引处理结果
* @param $data
* @param $table
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function wk_courses($data,$table)
{
$teacher=[];
if(!empty($data)){
$teacher_ids=array_column($data,'teacher_id');
$tmp=Db::name('teacher')->alias('t')
->join('wk_user_info u','t.user_id=u.pid','LEFT')
->where(['t.id'=>['in',join(',',$teacher_ids)]])->field('u.nickname,t.id')
->select()->toArray();
$teacher=array_combine(array_column($tmp,'id'),array_column($tmp,'nickname'));
}
$word=$this->scws($this->keyword);
foreach ($data as $key=>$val){
foreach ($val as $k=>$v){
if($k=='title'){
$str=cut(strip_tags( myTrim($val[$k])),185);
$data[$key][$k]=$this->replace_keyword($str,$word);
}
if($k=='content'){
$str=cut(strip_tags( myTrim($val[$k])),175);
$data[$key][$k]=$this->replace_keyword($str,$word);
}
if($k=='create_time'){
$data[$key]['create_time']=tranTime($v);
}
if($k=='teacher_id'){
if(array_key_exists($v,$teacher)){
$data[$key][$k]=$teacher[$v];
}else{$data[$key][$k]="未知";}
}
}
}
return $data;
}
/**
*
* @param $data
* @param $table
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function wk_article($data,$table)
{
$userInfo=$userAvatar=[];
if(!empty($data)){
$user_ids=array_column($data,'user_id');
$tmp=Db::name('user_info')
->where(['pid'=>['in',join(',',$user_ids)]])->field('nickname,pid,avatar')
->select()->toArray();
$userInfo=array_combine(array_column($tmp,'pid'),array_column($tmp,'nickname'));
$userAvatar=array_combine(array_column($tmp,'pid'),array_column($tmp,'avatar'));
}
$word=$this->scws($this->keyword);
foreach ($data as $key=>$val){
foreach ($val as $k=>$v){
if($k=='title'){
$str=cut(strip_tags( myTrim($val[$k])),185);
$data[$key][$k]=$this->replace_keyword($str,$word);
}
if($k=='content'){
$str=cut(strip_tags( myTrim($val[$k])),185);
$data[$key][$k]=$this->replace_keyword($str,$word);
}
if($k=='create_time'){
$data[$key]['create_time']=tranTime($v);
}
if($k=='user_id'){
if(array_key_exists($v,$userInfo)){
$data[$key]['nickname']=$userInfo[$v];
}else{$data[$key]['nickname']="未知";}
if(array_key_exists($v,$userAvatar)){
$data[$key]['avatar']=$userAvatar[$v];
}else{$data[$key]['avatar']="未知";}
}
}
}
return $data;
}
/**
*
* @param $data
* @param $table
* @return mixed
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\ModelNotFoundException
* @throws \think\exception\DbException
*/
public function wk_wenda_post($data,$table)
{
$reply_ids=$replyData=$userIds=[];
if(!empty($data)){
$ids=array_combine(array_column($data,'id'),array_column($data,'reply_status'));
foreach ($ids as $k=>$v){
if($ids[$k]==1){
array_push($reply_ids,$k);
}
}
if(!empty($reply_ids)){
foreach ($reply_ids as $v){
$tmpData=Db::query("select * from wk_wenda_reply where "
."id=(select id from wk_wenda_reply where ".
" pid=? order by `status` desc,id desc limit 1);",[$v]);
$tmp=$tmpData[0];
foreach ($tmp as $key=>$val){
if($key=='content'){
$tmp[$key]=str_ireplace($this->keyword,"<b>{$this->keyword}</b>",
cut(strip_tags( myTrim($tmp[$key])),185)
);
}
}
$replyData[$tmp['pid']]=$tmp;
array_push($userIds,$tmp['uid']);
}
if(!empty($userIds)){
$userData=Db::name('user_info')->where(['pid'=>['in',join(',',$userIds)]])
->field('pid,avatar,nickname')->select()->toArray();
foreach ($replyData as $k=>$v){
foreach ($userData as $kk=>$vv){
if($v['uid']==$vv['pid']){
$replyData[$k]['nickname']=$vv['nickname'];
$replyData[$k]['avatar']=$vv['avatar'];
}
}
}
}
}
}
$word=$this->scws($this->keyword);
foreach ($data as $key=>$val){
foreach ($val as $k=>$v){
if($k=='title'){
$str=cut(strip_tags( myTrim($val[$k])),185);
$data[$key][$k]=$this->replace_keyword($str,$word);
}
if($k=='content'){
$str=cut(strip_tags( myTrim($val[$k])),185);
$data[$key][$k]=$this->replace_keyword($str,$word);
}
if($k=='create_time'){
$data[$key]['create_time']=tranTime($v);
}
if($k=='id'){
if(array_key_exists($v,$replyData)){
$data[$key]['children']=$replyData[$v];
}else{
$data[$key]['children']="";
}
}
}
}
return $data;
}
/**
* 替换关键词
* @param $str
* @param array $word
* @return mixed
*/
public function replace_keyword($str,array $word)
{
foreach ($replace=range(1,count($word)) as $k=>$v) {
$replace[$k]="<b>$word[$k]</b>";
}
return str_ireplace($word,$replace,$str);
}
private function remark()
{
// $link=mysqli_connect("127.0.0.1","wk","wk123456");
// mysqli_select_db($link,'wk');
// $sql="select * from {$table} where id in({$ids})";
// $rst=mysqli_query($link,$sql);
// $data=[];
// while($row=mysqli_fetch_assoc($rst)){
// $tmp=[];
// $tmpData=$this->sphinx->BuildExcerpts($row, $table, $this->keyword, $opts);
// foreach ($tmpData as $k=>$v){
// $tmp[$fieldArr[$k]]=$v;
// }
// $tmp['db_type']=$this->tableArr[$table];
// $data[]=$tmp;
// }
// mysqli_close($link);
}
}
文章参考地址:https://blog.csdn.net/phphub/article/details/52325094