XunSearch.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. <?php
  2. /*
  3. * FecShop file.
  4. *
  5. * @link http://www.fecshop.com/
  6. * @copyright Copyright (c) 2016 FecShop Software LLC
  7. * @license http://www.fecshop.com/license/
  8. */
  9. namespace fecshop\services\search;
  10. //use fecshop\models\mongodb\Product;
  11. //use fecshop\models\xunsearch\Search as XunSearchModel;
  12. use fecshop\services\Service;
  13. use Yii;
  14. /**
  15. * Search XunSearch Service.
  16. * @author Terry Zhao <2358269014@qq.com>
  17. * @since 1.0
  18. */
  19. class XunSearch extends Service implements SearchInterface
  20. {
  21. public $searchIndexConfig;
  22. public $searchLang;
  23. public $fuzzy = false;
  24. public $synonyms = false;
  25. protected $_productModelName = '\fecshop\models\mongodb\Product';
  26. protected $_productModel;
  27. protected $_searchModelName = '\fecshop\models\xunsearch\Search';
  28. protected $_searchModel;
  29. public function init()
  30. {
  31. parent::init();
  32. list($this->_productModelName, $this->_productModel) = \Yii::mapGet($this->_productModelName);
  33. list($this->_searchModelName, $this->_searchModel) = \Yii::mapGet($this->_searchModelName);
  34. }
  35. /**
  36. * 初始化xunSearch索引.
  37. */
  38. protected function actionInitFullSearchIndex()
  39. {
  40. }
  41. /**
  42. * 将产品信息同步到xunSearch引擎中.
  43. */
  44. protected function actionSyncProductInfo($product_ids, $numPerPage)
  45. {
  46. if (is_array($product_ids) && !empty($product_ids)) {
  47. $productPrimaryKey = Yii::$service->product->getPrimaryKey();
  48. $xunSearchModel = new $this->_searchModelName();
  49. $filter['select'] = $xunSearchModel->attributes();
  50. $filter['asArray'] = true;
  51. $filter['where'][] = ['in', $productPrimaryKey, $product_ids];
  52. $filter['numPerPage'] = $numPerPage;
  53. $filter['pageNum'] = 1;
  54. $coll = Yii::$service->product->coll($filter);
  55. if (is_array($coll['coll']) && !empty($coll['coll'])) {
  56. foreach ($coll['coll'] as $one) {
  57. $one_name = $one['name'];
  58. $one_description = $one['description'];
  59. $one_short_description = $one['short_description'];
  60. if (!empty($this->searchLang) && is_array($this->searchLang)) {
  61. foreach ($this->searchLang as $langCode => $langName) {
  62. //echo $langCode;
  63. $xunSearchModel = new $this->_searchModelName();
  64. $xunSearchModel->_id = (string) $one['_id'];
  65. $one['name'] = Yii::$service->fecshoplang->getLangAttrVal($one_name, 'name', $langCode);
  66. $one['description'] = Yii::$service->fecshoplang->getLangAttrVal($one_description, 'description', $langCode);
  67. $one['short_description'] = Yii::$service->fecshoplang->getLangAttrVal($one_short_description, 'short_description', $langCode);
  68. $one['sync_updated_at'] = time();
  69. //echo $one['name']."\n";
  70. $serialize = true;
  71. Yii::$service->helper->ar->save($xunSearchModel, $one, $serialize);
  72. if ($errors = Yii::$service->helper->errors->get()) {
  73. // 报错。
  74. echo $errors;
  75. //return false;
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }
  82. //echo "XunSearch sync done ... \n";
  83. return true;
  84. }
  85. protected function actionDeleteNotActiveProduct($nowTimeStamp)
  86. {
  87. }
  88. /**
  89. * 删除在xunSearch的所有搜索数据,
  90. * 当您的产品有很多产品被删除了,但是在xunsearch 存在某些异常没有被删除
  91. * 您希望也被删除掉,那么,你可以通过这种方式批量删除掉产品
  92. * 然后重新跑一边同步脚本.
  93. */
  94. protected function actionXunDeleteAllProduct($numPerPage, $i)
  95. {
  96. //var_dump($index);
  97. $dbName = $this->_searchModel->projectName();
  98. // 删除索引
  99. Yii::$app->xunsearch->getDatabase($dbName)->getIndex()->clean();
  100. //$index = Yii::$app->xunsearch->getDatabase($dbName)->index;
  101. echo "begin delete Xun Search Date \n";
  102. $nowTimeStamp = (int) $nowTimeStamp;
  103. $XunSearchData = $this->_searchModel->find()
  104. ->limit($numPerPage)
  105. ->offset(($i - 1) * $numPerPage)
  106. ->all();
  107. foreach ($XunSearchData as $one) {
  108. $one->delete();
  109. }
  110. }
  111. /**
  112. * 得到搜索的产品列表.
  113. */
  114. protected function actionGetSearchProductColl($select, $where, $pageNum, $numPerPage, $product_search_max_count)
  115. {
  116. $collection = $this->fullTearchText($select, $where, $pageNum, $numPerPage, $product_search_max_count);
  117. $collection['coll'] = Yii::$service->category->product->convertToCategoryInfo($collection['coll']);
  118. //var_dump($collection);
  119. //exit;
  120. return $collection;
  121. }
  122. protected function fullTearchText($select, $where, $pageNum, $numPerPage, $product_search_max_count)
  123. {
  124. $enableStatus = Yii::$service->product->getEnableStatus();
  125. $searchText = $where['$text']['$search'];
  126. $productM = Yii::$service->product->getBySku($searchText);
  127. $productIds = [];
  128. if ($productM && $enableStatus == $productM['status']) {
  129. $productIds[] = $productM['_id'];
  130. } else {
  131. if (!isset($where['status'])) {
  132. $where['status'] = Yii::$service->product->getEnableStatus();
  133. }
  134. $XunSearchQuery = $this->_searchModel->find()->asArray();
  135. $XunSearchQuery->fuzzy($this->fuzzy);
  136. $XunSearchQuery->synonyms($this->synonyms);
  137. if (is_array($where) && !empty($where)) {
  138. if (isset($where['$text']['$search']) && $where['$text']['$search']) {
  139. $XunSearchQuery->where($where['$text']['$search']);
  140. } else {
  141. return [];
  142. }
  143. foreach ($where as $k => $v) {
  144. if ($k === '$text') {
  145. continue;
  146. }
  147. if (is_array($v)) {
  148. // 范围查询,类似 [ 'price' => [ '$gte'=> 100, '$lte' => 150 ] ] 的这种情况
  149. // 如果$k的值为`price`, 改为 `final_price`
  150. $k !== 'price' || $k = 'final_price';
  151. // 得到范围查询的开始和结束值,如果范围查询只有开始,譬如 x < 3, 那么范围的结束用空字符串'', 不能使用null,使用null会跑出异常, 详细参看
  152. // @vendor/hightman/xunsearch/wrapper/yii2-ext/QueryBuilder.php 281行函数。
  153. $rangBegin = isset($v['$gte']) ? $v['$gte'] : (isset($v['$gt']) ? $v['$gt'] : '');
  154. $rangEnd = isset($v['$lte']) ? $v['$lte'] : (isset($v['$lt']) ? $v['$lt'] : '');
  155. // 关于xunsearch的查询,参看:https://github.com/hightman/xs-sdk-php#%E6%A3%80%E7%B4%A2%E5%AF%B9%E8%B1%A1
  156. $XunSearchQuery->andWhere(['BETWEEN', $k, $rangBegin, $rangEnd]);
  157. } else {
  158. $XunSearchQuery->andWhere([$k => $v]);
  159. }
  160. }
  161. }
  162. $XunSearchQuery->orderBy(['score' => SORT_DESC]);
  163. $XunSearchQuery->limit($product_search_max_count);
  164. $XunSearchQuery->offset(0);
  165. $search_data = $XunSearchQuery->all();
  166. /**
  167. * 在搜索页面, spu相同的sku,是否只显示其中score高的sku,其他的sku隐藏
  168. * 如果设置为true,那么在搜索结果页面,spu相同,sku不同的产品,只会显示score最高的那个产品
  169. * 如果设置为false,那么在搜索结果页面,所有的sku都显示。
  170. * 这里做设置的好处,譬如服装,一个spu的不同颜色尺码可能几十个产品,都显示出来会占用很多的位置,对于这种产品您可以选择设置true
  171. * 这个针对的京东模式的产品
  172. */
  173. $data = [];
  174. if (Yii::$service->search->productSpuShowOnlyOneSku) {
  175. foreach ($search_data as $one) {
  176. if (!isset($data[$one['spu']])) {
  177. $data[$one['spu']] = $one;
  178. }
  179. }
  180. } else {
  181. $data = $search_data;
  182. }
  183. $count = count($data);
  184. $offset = ($pageNum - 1) * $numPerPage;
  185. $limit = $numPerPage;
  186. $productIds = [];
  187. foreach ($data as $d) {
  188. $productIds[] = new \MongoDB\BSON\ObjectId($d['_id']);
  189. }
  190. $productIds = array_slice($productIds, $offset, $limit);
  191. }
  192. if (!empty($productIds)) {
  193. $query = $this->_productModel->find()->asArray()
  194. ->select($select)
  195. ->where([
  196. '_id' => ['$in'=>$productIds],
  197. 'status' => Yii::$service->product->getEnableStatus(),
  198. ]);
  199. $data = $query->all();
  200. /**
  201. * 下面的代码的作用:将结果按照上面in查询的顺序进行数组的排序,使结果和上面的搜索结果排序一致(_id)。
  202. */
  203. $s_data = [];
  204. foreach ($data as $one) {
  205. $_id = (string) $one['_id'];
  206. if ($_id) {
  207. $s_data[$_id] = $one;
  208. }
  209. }
  210. $return_data = [];
  211. foreach ($productIds as $product_id) {
  212. $pid = (string) $product_id;
  213. if (isset($s_data[$pid]) && $s_data[$pid]) {
  214. $return_data[] = $s_data[$pid];
  215. }
  216. }
  217. return [
  218. 'coll' => $return_data,
  219. 'count'=> $count,
  220. ];
  221. }
  222. }
  223. /**
  224. * 得到搜索的sku列表侧栏的过滤.
  225. */
  226. protected function actionGetFrontSearchFilter($filter_attr, $where)
  227. {
  228. //var_dump($where);
  229. $dbName = $this->_searchModel->projectName();
  230. $_search = Yii::$app->xunsearch->getDatabase($dbName)->getSearch();
  231. $text = isset($where['$text']['$search']) ? $where['$text']['$search'] : '';
  232. if (!$text) {
  233. return [];
  234. }
  235. $sh = '';
  236. foreach ($where as $k => $v) {
  237. if ($k != '$text') {
  238. if (!$sh) {
  239. $sh = ' AND '.$k.':'.$v;
  240. } else {
  241. $sh .= ' AND '.$k.':'.$v;
  242. }
  243. }
  244. }
  245. //echo $sh;
  246. $docs = $_search->setQuery($text.$sh)
  247. ->setFacets([$filter_attr])
  248. ->setFuzzy($this->fuzzy)
  249. ->setAutoSynonyms($this->synonyms)
  250. ->search();
  251. $filter_attr_counts = $_search->getFacets($filter_attr);
  252. $count_arr = [];
  253. if (is_array($filter_attr_counts) && !empty($filter_attr_counts)) {
  254. foreach ($filter_attr_counts as $k => $v) {
  255. $count_arr[] = [
  256. '_id' => $k,
  257. 'count' => $v,
  258. ];
  259. }
  260. }
  261. return $count_arr;
  262. }
  263. /**
  264. * 通过product_id删除搜索数据.
  265. */
  266. protected function actionRemoveByProductId($product_id)
  267. {
  268. if (is_object($product_id)) {
  269. $product_id = (string) $product_id;
  270. $model = $this->_searchModel->findOne($product_id);
  271. if ($model) {
  272. $model->delete();
  273. }
  274. }
  275. }
  276. }