search.model.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. <?php
  2. /**
  3. * 搜索
  4. *
  5. *
  6. *
  7. *
  8. */
  9. defined('InShopNC') or exit('Access Invalid!');
  10. class searchModel
  11. {
  12. //是否开启分面搜索
  13. private $_open_face = false;
  14. //全文搜索对象
  15. private $_xs_search;
  16. //全文搜索对象
  17. private $_xs_index;
  18. //全文搜索到的商品ID数组
  19. private $_indexer_ids = array();
  20. //全文搜索到的品牌数组
  21. private $_indexer_brands = array();
  22. //全文检索到的商品分类数组
  23. private $_indexer_cates = array();
  24. //全文搜索结果总数
  25. private $_indexer_count;
  26. //搜索结果中品牌分面信息
  27. private $_face_brand = array();
  28. //搜索结果中品牌分面信息
  29. private $_face_attr = array();
  30. /**
  31. * 从全文索引库搜索关键词
  32. * @param unknown $condition 条件
  33. * @param unknown $order 排序
  34. * @param number $pagesize 每页显示商品数
  35. * @return
  36. */
  37. public function getIndexerList($condition = array(), $order = array(), $pagesize = 24)
  38. {
  39. try {
  40. //全文搜索初始化
  41. $this->_createXS($pagesize,C('fullindexer.appname'));
  42. //设置搜索内容
  43. $this->_setQueryXS($condition,$order);
  44. //执行搜索
  45. $this->_searchXS();
  46. return array($this->_indexer_ids, $this->_indexer_count);
  47. } catch (XSException $e) {
  48. // if (C('debug')) {
  49. // showMessage($e->getMessage(),'','html','error');
  50. // } else {
  51. Shopnc\Log::record('search\index'.$e->getMessage()."\r\n".$sql,Shopnc\Log::RUN);
  52. return false;
  53. // }
  54. }
  55. }
  56. /**
  57. * 设置全文检索查询条件
  58. * @param unknown $condition
  59. * @param array $order
  60. */
  61. private function _setQueryXS($condition,$order)
  62. {
  63. $condition['keyword'] = preg_replace("/([a-zA-Z0-9]{2,})/",' ${1} ',$condition['keyword']);
  64. $this->_xs_search->setQuery(is_null($condition['keyword']) ? '':$condition['keyword']);
  65. if (isset($condition['cate'])) {
  66. $this->_xs_search->addQueryString($condition['cate']['key'].':'.$condition['cate']['value']);
  67. } else {
  68. $this->_open_face = false;
  69. }
  70. if (isset($condition['brand_id'])) {
  71. $this->_xs_search->addQueryString('brand_id'.':'.$condition['brand_id']);
  72. }
  73. if (isset($condition['store_id'])) {
  74. $this->_xs_search->addQueryString('store_id'.':'.$condition['store_id']);
  75. }
  76. if (isset($condition['area_id'])) {
  77. $this->_xs_search->addQueryString('area_id'.':'.$condition['area_id']);
  78. }
  79. if (isset($condition['have_gift'])) {
  80. $this->_xs_search->addQueryString('have_gift'.':'.$condition['have_gift']);
  81. }
  82. if (is_array($condition['attr_id'])) {
  83. foreach ($condition['attr_id'] as $attr_id) {
  84. $this->_xs_search->addQueryString('attr_id'.':'.$attr_id);
  85. }
  86. }
  87. if (count($order) > 1) {
  88. $this->_xs_search->setMultiSort($order);
  89. } else {
  90. $this->_xs_search->setSort($order);
  91. }
  92. }
  93. /**
  94. * 创建全文搜索对象,并初始化基本参数
  95. * @param number $pagesize 每页显示商品数
  96. * @param string $appname 全文搜索INI配置文件名
  97. */
  98. private function _createXS($pagesize,$appname)
  99. {
  100. if (is_numeric($_GET['curpage']) && $_GET['curpage'] > 0) {
  101. $curpage = intval($_GET['curpage']);
  102. $start = ($curpage-1) * $pagesize;
  103. } else {
  104. $start = null;
  105. }
  106. require_once(BASE_DATA_PATH.'/api/xs/lib/XS.php');
  107. $obj_doc = new XSDocument();
  108. $obj_xs = new XS(C('fullindexer.appname'));
  109. $this->_xs_search = $obj_xs->search;
  110. $this->_xs_index = $obj_xs->index;
  111. $this->_xs_search->setCharset(CHARSET);
  112. $this->_xs_search->setLimit($pagesize,$start);
  113. //设置分面
  114. if ($this->_open_face) {
  115. $this->_xs_search->setFacets(array('brand_id','attr_id'));
  116. }
  117. }
  118. /**
  119. * 执行全文搜索
  120. */
  121. private function _searchXS()
  122. {
  123. // $goods_class = Model('goods_class')->getGoodsClassIndexedListAll();
  124. $docs = $this->_xs_search->search();
  125. $count = $this->_xs_search->getLastCount();
  126. $goods_ids = array();
  127. $brands = array();
  128. $cates = array();
  129. foreach ($docs as $k => $doc) {
  130. $goods_ids[] = $doc->goods_id;
  131. // if ($doc->brand_id > 0) {
  132. // $brands[$doc->brand_id]['brand_id'] = $doc->brand_id;
  133. // $brands[$doc->brand_id]['brand_name'] = $doc->brand_name;
  134. // }
  135. // if ($doc->gc_id > 0) {
  136. // $cates[$doc->gc_id]['gc_id'] = $doc->gc_id;
  137. // $cates[$doc->gc_id]['gc_name'] = $goods_class[$doc->gc_id]['gc_name'];
  138. // }
  139. }
  140. $this->_indexer_ids = $goods_ids;
  141. $this->_indexer_count = $count;
  142. $this->_indexer_brands = $brands;
  143. $this->_indexer_cates = $cates;
  144. //读取分面结果
  145. if ($this->_open_face) {
  146. $this->_face_brand = $this->_xs_search->getFacets('brand_id');
  147. $this->_face_attr = $this->_xs_search->getFacets('attr_id');
  148. $this->_parseFaceAttr($this->_face_attr);
  149. }
  150. return true;
  151. }
  152. /**
  153. * 处理属性多面信息
  154. */
  155. private function _parseFaceAttr($face_attr = array()) {
  156. if (!is_array($face_attr)) return;
  157. $new_attr = array();
  158. foreach ($face_attr as $k => $v) {
  159. $new_attr = array_merge($new_attr,explode('_',$k));
  160. }
  161. $this->_face_attr = $new_attr;
  162. }
  163. /**
  164. * 删除没有商品的品牌(不显示)
  165. * @param unknown $brand_array
  166. * @return unknown|multitype:
  167. */
  168. public function delInvalidBrand($brand_array = array()) {
  169. if (!$this->_open_face) return $brand_array;
  170. if (is_array($brand_array) && is_array($this->_face_brand)) {
  171. foreach ($brand_array as $k => $v) {
  172. if (!isset($this->_face_brand[$k])) {
  173. unset($brand_array[$k]);
  174. }
  175. }
  176. }
  177. return $brand_array;
  178. }
  179. /**
  180. * 删除没有商品的属性(不显示)
  181. * @param unknown $brand_array
  182. * @return unknown|multitype:
  183. */
  184. public function delInvalidAttr($attr_array = array()) {
  185. if (!$this->_open_face) return $attr_array;
  186. if (is_array($attr_array) && is_array($this->_face_attr)) {
  187. foreach ($attr_array as $key => $value) {
  188. if (is_array($value['value'])) {
  189. foreach ($value['value'] as $k => $v) {
  190. if (!in_array($k,$this->_face_attr)) {
  191. unset($attr_array[$key]['value'][$k]);
  192. }
  193. }
  194. }
  195. }
  196. }
  197. return $attr_array;
  198. }
  199. public function __get($key) {
  200. return $this->$key;
  201. }
  202. /**
  203. * 删除搜索索引中的无效商品
  204. * @param unknown $brand_array
  205. * @return unknown|multitype:
  206. */
  207. public function delInvalidGoods($goods_list, $indexer_ids = array()) {
  208. $goods_ids = array();
  209. foreach ($goods_list as $k => $v) {
  210. $goods_ids[] = $v['goods_id'];
  211. }
  212. $_diff_ids = array_diff($indexer_ids,$goods_ids);
  213. if (!empty($_diff_ids)) {
  214. file_put_contents(BASE_DATA_PATH.'/log/search.log',date('Y-m-d H:i:s',TIMESTAMP)."\r\n",FILE_APPEND);
  215. file_put_contents(BASE_DATA_PATH.'/log/search.log',implode(',',$indexer_ids)."\r\n",FILE_APPEND);
  216. file_put_contents(BASE_DATA_PATH.'/log/search.log',implode(',',$goods_ids)."\r\n",FILE_APPEND);
  217. file_put_contents(BASE_DATA_PATH.'/log/search.log',implode(',',$_diff_ids)."\r\n\r\n",FILE_APPEND);
  218. // $this->_xs_index->del($_diff_ids);
  219. // QueueClient::push('flushIndexer', '');
  220. }
  221. }
  222. /**
  223. * 取得商品分类详细信息
  224. *
  225. * @param array $search_param 需要的参数内容
  226. * @return array 数组类型的返回结果
  227. */
  228. public function getAttr($get, $default_classid){
  229. if(!empty($get['a_id'])){
  230. $attr_ids = explode('_', $get['a_id']);
  231. }
  232. $data = array();
  233. $model_type = Model('type');
  234. // 获取当前的分类内容
  235. $class_array = Model('goods_class')->getGoodsClassForCacheModel();
  236. $data['class'] = $class_array[$get['cate_id']];
  237. if (empty($data['class']['child']) && empty($data['class']['childchild'])) {
  238. // 根据属性查找商品
  239. if (is_array($attr_ids)) {
  240. // 商品id数组
  241. $goodsid_array = array();
  242. $data['sign'] = false;
  243. foreach ($attr_ids as $val) {
  244. $where = array();
  245. $where['attr_value_id'] = $val;
  246. if ($data['sign']) {
  247. $where['goods_id'] = array('in', $goodsid_array);
  248. }
  249. $goodsattrindex_list = Model('goods_attr_index')->getGoodsAttrIndexList($where, 'goods_id');
  250. if (!empty($goodsattrindex_list)) {
  251. $data['sign'] = true;
  252. $tpl_goodsid_array = array();
  253. foreach ($goodsattrindex_list as $val) {
  254. $tpl_goodsid_array[] = $val['goods_id'];
  255. }
  256. $goodsid_array = $tpl_goodsid_array;
  257. } else {
  258. $data['goodsid_array'] = $goodsid_array = array();
  259. $data['sign'] = false;
  260. break;
  261. }
  262. }
  263. if ($data['sign']) {
  264. $data['goodsid_array'] = $goodsid_array;
  265. }
  266. }
  267. }
  268. $class = $class_array[$default_classid];
  269. if (empty($class['child']) && empty($class['childchild'])) {
  270. //获得分类对应的类型ID
  271. $type_id = $class['type_id'];
  272. //品牌列表
  273. $typebrand_list = Model('type')->getTypeBrandList(array('type_id' => $type_id), 'brand_id');
  274. if (!empty($typebrand_list)) {
  275. $brandid_array = array();
  276. foreach ($typebrand_list as $val) {
  277. $brandid_array[] = $val['brand_id'];
  278. }
  279. $brand_array = Model('brand')->getBrandPassedList(array('brand_id' => array('in', $brandid_array)), 'brand_id,brand_name');
  280. $brand_array = array_under_reset($brand_array, 'brand_id');
  281. }
  282. // 被选中的品牌
  283. $brand_id = intval($get['b_id']);
  284. if ($brand_id > 0 && !empty($brand_array)){
  285. $checked_brand = array();
  286. if(isset($brand_array[$brand_id])){
  287. $checked_brand[$brand_id]['brand_name'] = $brand_array[$brand_id]['brand_name'];
  288. }
  289. }
  290. //属性列表
  291. $model_attribute = Model('attribute');
  292. $attribute_list = $model_attribute->getAttributeShowList(array('type_id' => $type_id), 'attr_id,attr_name');
  293. $attributevalue_list = $model_attribute->getAttributeValueList(array('type_id' => $type_id), 'attr_value_id,attr_value_name,attr_id');
  294. $attributevalue_list = array_under_reset($attributevalue_list, 'attr_id', 2);
  295. $attr_array = array();
  296. if (!empty($attribute_list)) {
  297. foreach ($attribute_list as $val) {
  298. $attr_array[$val['attr_id']]['name'] = $val['attr_name'];
  299. $tpl_array = array_under_reset($attributevalue_list[$val['attr_id']], 'attr_value_id');
  300. $attr_array[$val['attr_id']]['value'] = $tpl_array;
  301. }
  302. }
  303. // 被选中的属性
  304. if(is_array($attr_ids) && !empty($attr_array)){
  305. $checked_attr = array();
  306. foreach ($attr_ids as $s){
  307. foreach ($attr_array as $k=>$d){
  308. if(isset($d['value'][$s])){
  309. $checked_attr[$k]['attr_name'] = $d['name'];
  310. $checked_attr[$k]['attr_value_id'] = $s;
  311. $checked_attr[$k]['attr_value_name']= $d['value'][$s]['attr_value_name'];
  312. }
  313. }
  314. }
  315. }
  316. if (C('fullindexer.open')) {
  317. $brand_array = $this->delInvalidBrand($brand_array);
  318. $attr_array = $this->delInvalidAttr($attr_array);
  319. }
  320. }
  321. return array($data, $brand_array, $attr_array, $checked_brand, $checked_attr);
  322. }
  323. /**
  324. * 从TAG中查找分类
  325. */
  326. public function getTagCategory($keyword = '') {
  327. if ($keyword != '') {
  328. // 跟据class_tag缓存搜索出与keyword相关的分类
  329. $tag_list = rkcache('class_tag', true);
  330. if (!empty($tag_list) && is_array($tag_list)) {
  331. foreach($tag_list as $key => $val) {
  332. $tag_value = str_replace(',', '==ShopNC==', $val['gc_tag_value']);
  333. if (strpos($tag_value, $keyword)) {
  334. $data[] = $val['gc_id'];
  335. }
  336. }
  337. }
  338. }
  339. return $data;
  340. }
  341. /**
  342. * 获取父级分类,递归调用
  343. */
  344. private function _getParentCategory($gc_id, $goods_class, $data) {
  345. array_unshift($data, $gc_id);
  346. if ($goods_class[$gc_id]['gc_parent_id'] != 0) {
  347. return $this->_getParentCategory($goods_class[$gc_id]['gc_parent_id'], $goods_class, $data);
  348. } else {
  349. return $data;
  350. }
  351. }
  352. /**
  353. * 显示左侧商品分类
  354. * @param array $param 分类id
  355. * @sign int $sign 0为取得最后一级的同级分类,1为不取得
  356. */
  357. public function getLeftCategory($param, $sign = 0) {
  358. $data = array();
  359. if (!empty($param)) {
  360. $goods_class = Model('goods_class')->getGoodsClassForCacheModel();
  361. foreach ($param as $val) {
  362. $data[] = $this->_getParentCategory($val, $goods_class, array());
  363. }
  364. }
  365. $tpl_data = array();
  366. $gc_list = Model('goods_class')->get_all_category();
  367. foreach ($data as $value) {
  368. //$tpl_data[$val[0]][$val[1]][$val[2]] = $val[2];
  369. if (!empty($gc_list[$value[0]])){ // 一级
  370. $tpl_data[$value[0]]['gc_id'] = $gc_list[$value[0]]['gc_id'];
  371. $tpl_data[$value[0]]['gc_name'] = $gc_list[$value[0]]['gc_name'];
  372. if (!empty($gc_list[$value[0]]['class2'][$value[1]])) { // 二级
  373. $tpl_data[$value[0]]['class2'][$value[1]]['gc_id'] = $gc_list[$value[0]]['class2'][$value[1]]['gc_id'];
  374. $tpl_data[$value[0]]['class2'][$value[1]]['gc_name'] = $gc_list[$value[0]]['class2'][$value[1]]['gc_name'];
  375. if (!empty($gc_list[$value[0]]['class2'][$value[1]]['class3'][$value[2]])) { // 三级
  376. $tpl_data[$value[0]]['class2'][$value[1]]['class3'][$value[2]]['gc_id'] = $gc_list[$value[0]]['class2'][$value[1]]['class3'][$value[2]]['gc_id'];
  377. $tpl_data[$value[0]]['class2'][$value[1]]['class3'][$value[2]]['gc_name'] = $gc_list[$value[0]]['class2'][$value[1]]['class3'][$value[2]]['gc_name'];
  378. if (!$sign) { // 取得全部三级分类
  379. foreach ($gc_list[$value[0]]['class2'][$value[1]]['class3'] as $val) {
  380. $tpl_data[$value[0]]['class2'][$value[1]]['class3'][$val['gc_id']]['gc_id'] = $val['gc_id'];
  381. $tpl_data[$value[0]]['class2'][$value[1]]['class3'][$val['gc_id']]['gc_name'] = $val['gc_name'];
  382. if ($value[2] == $val['gc_id']) {
  383. $tpl_data[$value[0]]['class2'][$value[1]]['class3'][$val['gc_id']]['default'] = 1;
  384. }
  385. }
  386. }
  387. } else { // 取得全部二级分类
  388. if (!$sign) { // 取得同级分类
  389. if (!empty($gc_list[$value[0]]['class2'])) {
  390. foreach ($gc_list[$value[0]]['class2'] as $gc2) {
  391. $tpl_data[$value[0]]['class2'][$gc2['gc_id']]['gc_id'] = $gc2['gc_id'];
  392. $tpl_data[$value[0]]['class2'][$gc2['gc_id']]['gc_name'] = $gc2['gc_name'];
  393. if (!empty($gc2['class3'])) {
  394. foreach ($gc2['class3'] as $gc3) {
  395. $tpl_data[$value[0]]['class2'][$gc2['gc_id']]['class3'][$gc3['gc_id']]['gc_id'] = $gc3['gc_id'];
  396. $tpl_data[$value[0]]['class2'][$gc2['gc_id']]['class3'][$gc3['gc_id']]['gc_name'] = $gc3['gc_name'];
  397. }
  398. }
  399. }
  400. }
  401. }
  402. }
  403. } else { // 取得全部一级分类
  404. if (!$sign) { // 取得同级分类
  405. if (!empty($gc_list)) {
  406. foreach ($gc_list as $gc1) {
  407. $tpl_data[$gc1['gc_id']]['gc_id'] = $gc1['gc_id'];
  408. $tpl_data[$gc1['gc_id']]['gc_name'] = $gc1['gc_name'];
  409. if (!empty($gc1['class2'])) {
  410. foreach ($gc1['class2'] as $gc2) {
  411. $tpl_data[$gc1['gc_id']]['class2'][$gc2['gc_id']]['gc_id'] = $gc2['gc_id'];
  412. $tpl_data[$gc1['gc_id']]['class2'][$gc2['gc_id']]['gc_name'] = $gc2['gc_name'];
  413. if (!empty($gc2['class3'])) {
  414. foreach ($gc2['class3'] as $gc3) {
  415. $tpl_data[$gc1['gc_id']]['class2'][$gc2['gc_id']]['class3'][$gc3['gc_id']]['gc_id'] = $gc3['gc_id'];
  416. $tpl_data[$gc1['gc_id']]['class2'][$gc2['gc_id']]['class3'][$gc3['gc_id']]['gc_name'] = $gc3['gc_name'];
  417. }
  418. }
  419. }
  420. }
  421. }
  422. }
  423. }
  424. }
  425. }
  426. }
  427. return $tpl_data;
  428. }
  429. /**
  430. * 全文搜索
  431. * @return array 商品主键,搜索结果总数
  432. */
  433. public function indexerSearch($get = array(),$pagesize) {
  434. if (!C('fullindexer.open')) return array(null,0);
  435. $condition = array();
  436. //拼接条件
  437. if (intval($get['cate_id']) > 0) {
  438. $cate_id = intval($get['cate_id']);
  439. } elseif (intval($get['gc_id']) > 0) {
  440. $cate_id = intval($get['gc_id']);
  441. }
  442. if ($cate_id) {
  443. $goods_class = Model('goods_class')->getGoodsClassForCacheModel();
  444. $depth = $goods_class[$cate_id]['depth'];
  445. $cate_field = 'cate_'.$depth;
  446. $condition['cate']['key'] = $cate_field;
  447. $condition['cate']['value'] = $cate_id;
  448. }
  449. if ($get['keyword'] != '') {
  450. $condition['keyword'] = $get['keyword'];
  451. }
  452. if (intval($get['b_id']) > 0) {
  453. $condition['brand_id'] = intval($get['b_id']);
  454. }
  455. if (preg_match('/^[\d_]+$/',$get['a_id'])) {
  456. $attr_ids = explode('_',$get['a_id']);
  457. if (is_array($attr_ids)){
  458. foreach ($attr_ids as $v) {
  459. if (intval($v) > 0) {
  460. $condition['attr_id'][] = intval($v);
  461. }
  462. }
  463. }
  464. }
  465. if ($get['type'] == 1) {
  466. $condition['store_id'] = 1;
  467. }
  468. if (intval($get['area_id']) > 0) {
  469. $condition['area_id'] = intval($get['area_id']);
  470. }
  471. if ($get['gift'] == 1) {
  472. $condition['have_gift'] = 1;
  473. }
  474. //拼接排序(销量,浏览量,价格)
  475. $order = array();
  476. $order = array('store_id' => false,'goods_id' => false);
  477. if (in_array($get['key'],array('1','2','3'))) {
  478. $order = array(str_replace(array('1','2','3'), array('goods_salenum','goods_click','goods_price'), $get['key'])
  479. => $get['order'] == '1' ? true : false
  480. );
  481. }
  482. //取得商品主键等信息
  483. $result = $this->getIndexerList($condition,$order,$pagesize);
  484. if ($result !== false) {
  485. list($indexer_ids,$indexer_count) = $result;
  486. //如果全文搜索发生错误,后面会再执行数据库搜索
  487. } else {
  488. $indexer_ids = null;
  489. $indexer_count = 0;
  490. }
  491. return array($indexer_ids,$indexer_count);
  492. }
  493. }