class AddKNowledgeAction extends CAction { //add and update public function actionPost() { if ($_POST) { //如果是post操作 $res = array('code'=>0,'message'=>''); $kid = Yii::app()->request->getPost('kid'); //这里是知识主键id $cid = Yii::app()->request->getPost('cid'); $title = Yii::app()->request->getPost('title'); $content = Yii::app()->request->getPost('content'); $auth_group = Yii::app()->request->getPost('auth_group'); $end_time = Yii::app()->request->getPost('end_time'); $keywords = Yii::app()->request->getPost('keywords'); if (empty($kid)) { //$kid不存在则说明是走add操作,否则是update if ($kid = CallCenterService::addKNowledge($cid, $title, $content, $auth_group, $end_time, $keywords)) { //这里表示添加成功 } } else { if (CallCenterService::updateKNowledge($kid, $cid, $title, $content, $auth_group, $end_time, $keywords)) { //这里表示修改成功 } } } } //delete public function actionDelete (){ $kid = Yii::app()->request->getQuery('kid'); $action = Yii::app()->request->getQuery('action'); //action => ['delete' => '物理删除', 'Invalid' => '逻辑删除']if ($kid && $action) { if (CallCenterService::delKNowledge($kid, $action)) { //表示删除成功 } } } }
//服务层 class CallCenterService { private static $instance; public static $auth_group = null; public static function getInstance() { if (empty(self::$instance)) { self::$instance = new CallCenterService(); } return self::$instance; } /** * 添加知识 */ public static function addKNowledge($cid, $title, $content, $auth_group, $end_time, $keywords) { $model = new KNowledgenew; $operator = Yii::app()->user->id; $created = date('Y-m-d H:i:s'); $model->attributes = array( 'cid' => $cid, 'title' => $title, 'content' => $content, 'operator' => $operator, 'created' => $created, 'auth_group' => $auth_group, 'end_time' => $end_time, 'keywords' => $keywords, 'updated' => $created ); if ($model->save()) { $id = $model->id; //异步添加索引到es KNowledgenew::onKNowledgeChanged('add', array('id' => $id)); return $id; } else { } return false; } /** * 编辑知识 */ public static function updateKNowledge($kid, $cid, $title, $content , $auth_group, $end_time, $keywords) { $kNowledge = KNowledgenew::getKNowledge($kid); if ($kNowledge) { $model = new KNowledgenew; $model->updateByPk($kid, array( 'cid' => $cid, 'title' => $title, 'content' => $content, 'auth_group' => $auth_group, 'end_time' => isset($end_time) && !empty($end_time) ? $end_time : null, 'keywords' => $keywords, 'updated' => date('Y-m-d H:i:s') ) ); KNowledgenew::onKNowledgeChanged('update', array('id' => $kid)); return true; } return false; } /**删除一条知识 * @param $kid * @param string $action Invalid => 逻辑删除 ,delete =>物理删除 * @return bool */ public static function delKNowledge($kid, $action = 'invalid') { $kNowledge = KNowledgenew::getKNowledge($kid); if ($kNowledge) { $model = new KNowledgenew; if ($action == 'delete') { $model->deleteByPk($kid); } else { $model->updateByPk($kid,array('status'=>KNowledgenew::STATUS_DEL)); } //更新es KNowledgenew::onKNowledgeChanged('delete', array('id' => $kid)); //删除收藏夹中的相关知识 KNowledgenewCollection::model()->deleteall("kid = $kid"); return true; } return false; } }
三、Model层的更新点击浏览次数场景及异步任务更新ES信息
//model层 class KNowledgenew extends CActiveRecord { const STATUS_norMAL = 1; const STATUS_DEL = 2; //Invalid public function tableName() { return '{{kNowledgenew}}'; } public static function model($className=__CLASS__) { return parent::model($className); } /** * 增加浏览数 */ public static function addClickNum($kid) { $model = self::model(); $model->updateCounters(array('click_num'=>1),'id=:id',array(':id'=>$kid)); KNowledgenew::onKNowledgeChanged('update', array('id' => $kid)); return true; } //更新es信息 public static function onKNowledgeChanged($action, $param){ //echo '更新知识库索引action='.$action.PHP_EOL; EdjLog::info('更新知识库索引action='.$action); $base_param = array('es_source' => 'KNowledgenew', 'es_action' => $action); Queue::model()->putin( //异步 array( 'method'=>'synchronize_elasticsearch', 'params'=>array_merge($base_param, $param) ), 'synchronize_elasticsearch' ); } }
四、异步Job队列生产
<?PHP /** * 基于redis的queue队列 */ class Queue { private static $_models; public $queue_max_length = array( ); public static function model($className=__CLASS__) { $model=null; if (isset(self::$_models[$className])) $model=self::$_models[$className]; else { $model=self::$_models[$className]=new $className(null); } return $model; } //确定redis private function select_redis($type) { return QueuePool::model()->get_zone($type); } private function trim($queue_name) { $type = str_replace("queue_", "", $queue_name); $max = 0; if (isset($this->queue_max_length[$type])) { $max = intval($this->queue_max_length[$type]); } if ($max>0) { $zone = $this->select_redis($type); if($zone) { $zone['redis']->lTrim($queue_name, 0, $max-1); } else { EdjLog::error("can not find zone, queue name: " . $type); return; } } } /** * 放入队列,统一队列对外暴露方法,增加类型默认放task队列,指定了就放对应的队列,同时如果不在指定类型内的,也放默认队列 * * @param unkNown_type $params * @param unkNown_type $type * @return mixed */ public function putin($params=null, $type){ $type = empty($type) ? 'error' : strtolower($type); $base_qname = QNameManagerService::model()->get_base_qname($type); if(!empty($base_qname)) { $this->queue_name = 'queue_'.$base_qname; }else{ $this->queue_name = 'queue_error'; } if ($params===null) { return $this->get(); } else { return $this->add($params); //如果add替换为processtask方法,则同步 } } /** * 取一条队列数据,封装多个队列,统一调用方法 * @param string $type * @return array */ public function getit($type='default') { $base_qname = QNameManagerService::model()->get_base_qname($type); if(!empty($base_qname)) { $this->queue_name = 'queue_'.$base_qname; }else{ return array(); } $zone = $this->select_redis($type); if($zone) { if($zone['brpop']) { $json = ''; $result = $zone['redis']->brPop($this->queue_name, $zone['brpop']); if(!empty($result) && isset($result[1])) { $json = $result[1]; } } else { $json = $zone['redis']->rPop($this->queue_name); } } else { EdjLog::error("can not find zone, queue name: " . $type); return array(); } return json_decode($json, true); } /** * 返回队列接收的类型列表 * @return array */ public function getQueueTypeList() { $list = QNameManager::model()->findall(); if($list) { return $list; } EdjLog::error("Error: get queue list from database"); return array(); } /** * 设置或者读取位置队列 * @param array $params * @return mixed */ public function position($params=null) { $this->queue_name='queue_position'; if ($params===null) { return $this->get(); } else { return $this->add($params); } } /** * 心跳队列 * @param string $params * @return mixed */ public function heartbeat($params=null) { $this->queue_name='queue_heartbeat'; if ($params===null) { return $this->get(); } else { return $this->add($params); } } /** * 最高优先级队列 * @param string $params * @return mixed */ public function task($params=null) { $this->queue_name='queue_task'; if ($params===null) { return $this->get(); } else { return $this->add($params); } } /** * 保存日志到数据库 * @param string $params * @return mixed */ public function dumplog($params=null) { $this->queue_name='queue_dumplog'; if ($params===null) { return $this->get(); } else { return $this->add($params); } } /** * 返回各个队列中的任务总数 */ public function length() { $queue = $this->getQueueTypeList(); $queue_length=array(); $reg = "/P[0-9]+$/"; foreach($queue as $item) { $base_qname = $item->base_qname; $zone = $this->select_redis($base_qname); $key = 'queue_'.$base_qname; if($zone) { $len = $zone['redis']->lLen($key); if(isset($item->max) && $len > $item->max) { $key = '!'.$key; } $pkey = ''; if(preg_match($reg, $zone['name'])) { $pkey = $key.'@'.$zone['name']; } else { $pkey = $key.'@'.$zone['name']."_P".$item->level; } $queue_length[$pkey] = $len; } else { EdjLog::error("can not find zone, queue name: " . $key); } } return $queue_length; } private function get() { $type = str_replace("queue_", "", $this->queue_name); $zone = $this->select_redis($type); if($zone) { if($zone['brpop']) { $json = ''; $result = $zone['redis']->brPop($this->queue_name, $zone['brpop']); if(!empty($result) && isset($result[1])) { $json = $result[1]; } } else { $json = $zone['redis']->rPop($this->queue_name); } } else { EdjLog::error("can not find zone, queue name: " . $type); return array(); } return json_decode($json, true); } private function add($params) { $json=json_encode($params); $type = str_replace("queue_", "", $this->queue_name); $zone = $this->select_redis($type); $return = 0; if($zone) { try { $return = $zone['redis']->lPush($this->queue_name, $json); } catch (Exception $e) { EdjLog::error("write redis error,msg:".$e->getMessage()); //echo $e->getMessage(); } } else { EdjLog::error("can not find zone, queue name: " . $type); } return $return; } //如果add 替换为此方法,则同步 public function processtask($task) { if(!isset($task['method'], $task['params'])) { $task_content = json_encode($task); EdjLog::error("can not run task due to no 'method' or 'params' specified, task is $task_content"); return; } $method=$task['method']; $params=$task['params']; $class = isset($task['class']) ? $task['class'] : "QueueProcess"; EdjLog::info("REdis_QUEUE_OUT CLASS:$class METHOD:$method ParaMS:".json_encode($params)); try { //throw new Exception("Value must be 1 or below"); $queue_process=new $class(); // check this method is exist, if not throw ReflectionException new ReflectionMethod($queue_process, $method); call_user_func_array(array($queue_process, $method), array($params)); } catch(Exception $e) { $errmsg = $e->getMessage(); EdjLog::error("execption queue_run method:$method err: $errmsg"); } } public function getLengthByType($type){ $type = empty($type) ? 'error' : strtolower($type); $base_qname = QNameManagerService::model()->get_base_qname($type); $zone = $this->select_redis($base_qname); $key = 'queue_'.$base_qname; $len = 0; if($zone) { $len = $zone['redis']->lLen($key); } else { EdjLog::error("can not find zone, queue name: " . $base_qname); } return $len; } }
五、异步Job队列消费
<?PHP /** * 队列处理 */ Yii::import("application.ecenter.service.HttpUtils"); class QueueProcess { private static $_models; private $message; public static function model($className=__CLASS__) { $model=null; if (isset(self::$_models[$className])) $model=self::$_models[$className]; else { $model=self::$_models[$className]=new $className(null); } return $model; } public function synchronize_elasticsearch($param) { if (empty($param) || !isset($param['es_source'], $param['es_action'])) { return false; } $class_name = $param['es_source'].'Synchronizer'; $method_name = $param['es_action']; if (class_exists($class_name) && method_exists($class_name, $method_name)) { unset($param['es_source']); unset($param['es_action']); call_user_func(array($class_name, $method_name), $param); } else { EdjLog::error('synchronize method does not exist. class name '.$class_name.' method name '.$method_name); } } }
六、ES信息处理操作服务层
<?PHP /** * Created by PHPStorm. */ class KNowledgenewSynchronizer { static public $index = 'kNowledge_index'; static public $type = 'kNowledge'; static public $filed = ' id, keywords, title, content, auth_group, cid, operator, click_num, status, created, updated '; static public function add($param) { if (empty($param) || !isset($param['id'])) { return false; } $id = $param['id']; $sql = "select".self::$filed."from t_kNowledgenew where id=:id"; $doc = Yii::app()->db->CreateCommand($sql)->queryRow(true,array('id'=>$id)); if (empty($doc)) { EdjLog::error('cannot find kNowledge with id: '.$id); return false; } return ElasticsearchSynchronizer::addDocument(self::$index, self::$type, $id, $doc); } static public function delete($param) { if (empty($param) || !isset($param['id'])) { return false; } $id = $param['id']; return ElasticsearchSynchronizer::deleteDocument(self::$index, self::$type, $id); } static public function update($param) { if (empty($param) || !isset($param['id'])) { return false; } $id = $param['id']; $sql = "select".self::$filed."from t_kNowledgenew where id=:id"; $doc = Yii::app()->db->CreateCommand($sql)->queryRow(true,array('id'=>$id)); if (empty($doc)) { EdjLog::error('cannot find kNowledge with id: '.$id); return false; } return ElasticsearchSynchronizer::updateDocument(self::$index, self::$type, $id, $doc); } }
七、ES信息处理操作Model层
<?PHP use Elastica\Client; use Elastica\Query\QueryString; use Elastica\Query; use Elastica\Document; Class ElasticsearchSynchronizer {//测试 //const ES_HOST='search.n.edaijia.cn'; //const ES_PORT=9200; static public function addDocument($index, $type, $id, $doc) { $client = new Client(array('host' => self::ES_HOST, self::ES_PORT)); $type = $client->getIndex($index)->getType($type); try { $response = $type->addDocument(new Document($id, $doc)); if ($response->isOk()) { EdjLog::info("add document $id succeeded"); return true; } else { EdjLog::info("add document $id Failed"); return false; } } catch (Exception $e) { print_r($e); EdjLog::error("add document $id Failed with exception ".$e->getMessage()); return false; } } static public function updateDocument($index, $type, $id, $doc) { $client = new Client(array('host' => self::ES_HOST, 'port' => self::ES_PORT)); try { $response = $client->getIndex($index) ->getType($type) ->updateDocument(new Document($id, $doc)); if ($response->isOk()) { EdjLog::info("update document $id succeeded"); return true; } else { EdjLog::info("update document $id Failed"); return false; } } catch (Exception $e) { EdjLog::error("update document $id Failed with exception ".$e->getMessage()); return false; } } static public function deleteDocument($index, $type, $id) { $client = new Client(array('host' => self::ES_HOST, 'port' => self::ES_PORT)); try { $response = $client->getIndex($index)->getType($type)->deleteById($id); if ($response->isOk()) { EdjLog::info("delete document $id succeeded"); return true; } else { EdjLog::info("delete document $id Failed"); return false; } } catch (Exception $e) { EdjLog::error("delete document $id Failed with exception ".$e->getMessage()); return false; } } }
八、查询
/** * @param $keyword * @param int $page * @param int $size * @param str $search_type * 搜索知识 * 搜索标题和内容,多个关键词是并且关系,空格分隔 */ public static function searchKNowledge($keyword, $page = 0, $size = 50, $search_type = 'default') { //对搜索关键词空格隔开 // $keywords = explode(' ',trim($keyword)); $start = $page * $size; $client = new \Elastica\Client(array('host' => ElasticsearchSynchronizer::ES_HOST, 'port' => ElasticsearchSynchronizer::ES_PORT));//更改成线上主机和端口 $search = new \Elastica\Search($client); $search ->addindex(KNowledgenewSynchronizer::$index)->addType(KNowledgenewSynchronizer::$type); // $query = new \Elastica\Query\Bool(); $query = new \Elastica\Query(); //设置必要查询 // foreach($keywords as $word) { // if($word) { // $kNowledge_query = new \Elastica\Query\QueryString(); // $kNowledge_query->setFields(array('title', 'content')); // $kNowledge_query->setQuery('"' . $word . '"'); // $query->addMust($kNowledge_query); // } // } $MultiMatch_obj = new \Elastica\Query\Multimatch(); $MultiMatch_obj->setQuery($keyword); if ($search_type == 'default') { $MultiMatch_obj->setFields(array('keywords')); } else { $MultiMatch_obj->setTieBreaker(0.3); $MultiMatch_obj->setType('best_fields'); $MultiMatch_obj->setFields(array('keywords^901209','content')); $MultiMatch_obj->setoperator('or'); //这里是字符串,在es的扩展目录下 setMinimumShouldMatch方法把转int去掉// //$this->setParam('minimum_should_match', (int)$minimumShouldMatch); $MultiMatch_obj->setMinimumShouldMatch('30%'); } $query->setQuery($MultiMatch_obj); //命中全部纪录 $query = \Elastica\Query::create($query); $query->setSource(["id","cid","updated", "title", 'keywords',"content",'auth_group','status']); // $query->setSort([ // 'click_num' => ['order' => 'desc'] // ]); //设置起始页 $query->setFrom($start); $query->setSize($size); //设置高亮显示 $query->setHighlight(array( 'pre_tags' => array('<span style="color: red">'), 'post_tags' => array('</span>'), 'fields' => array( 'title' => array( 'fragment_size' => 5,//包含高亮词语,最多展示多少个 'number_of_fragments' => 1,//分几段展示,这里一段就可以 ), 'keywords' => array( 'fragment_size' => 10,//包含高亮词语,最多展示多少个 'number_of_fragments' => 1,//分几段展示,这里一段就可以 ), 'content' => array( 'fragment_size' => 10,//包含高亮词语,最多展示多少个 'number_of_fragments' => 1,//分几段展示,这里一段就可以 ), ), )); $search->setQuery($query); $results = array(); $totalResults = 0; try { $resultSet = $search->search(); $results = $resultSet->getResults(); $totalResults = $resultSet->getTotalHits(); } catch (Exception $e) { EdjLog::error("query elasticsearch Failed"); } if ($totalResults <= 0) { return; } $poi_result = array(); foreach ($results as $result) { $highlight = $result->getHighlights(); $title_hightlight = isset($highlight['title'][0])?$highlight['title'][0]:''; $content_hightlight = isset($highlight['content'][0])?$highlight['content'][0]:''; $keywords_highlight = isset($highlight['keywords'][0])?$highlight['keywords'][0]:''; $poi_result[] = array( 'id' => $result->id, 'cid' => $result->cid, 'title' => $result->title, 'keywords' => $result->keywords, 'content' => $result->content, 'auth_group' => $result->auth_group, 'title_highlight'=>$title_hightlight, //高亮展示标题搜索关键词 'keywords_highlight'=>$keywords_highlight, //高亮展示标题搜索关键词 'content_highlight'=>$content_hightlight,//高亮展示内容搜索关键词 'updated'=>$result->updated, 'status' => $result->status, ); } //这里过滤了用户非权限列表 $poi_result = self::filterNoAuthKNowledge($poi_result); $totalResults = count($poi_result) ; $info = array('totalNum'=>$totalResults,'data'=>$poi_result); return json_encode($info); }
九、源码包
链接:https://pan.baidu.com/s/1lVcrb50HSLrJh3zvBOdH5g 提取码:9c9c
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。