MysqlSearch.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345
  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\mongodb\Search;
  12. use fecshop\services\Service;
  13. use Yii;
  14. /**
  15. * Search MongoSearch Service
  16. * @author Terry Zhao <2358269014@qq.com>
  17. * @since 1.0
  18. */
  19. class MysqlSearch extends Service implements SearchInterface
  20. {
  21. public $searchIndexConfig;
  22. //public $searchLang;
  23. public $enable;
  24. //protected $_productModelName = '\fecshop\models\mongodb\Product';
  25. //protected $_productModel;
  26. protected $_searchModelName = '\fecshop\models\mysqldb\Search';
  27. protected $_searchModel;
  28. public function init()
  29. {
  30. parent::init();
  31. //list($this->_productModelName, $this->_productModel) = \Yii::mapGet($this->_productModelName);
  32. list($this->_searchModelName, $this->_searchModel) = \Yii::mapGet($this->_searchModelName);
  33. }
  34. /**
  35. * 创建索引. (mysql不需要)
  36. */
  37. protected function actionInitFullSearchIndex()
  38. {
  39. return;
  40. }
  41. //
  42. protected function getProductSelectData()
  43. {
  44. $productPrimaryKey = Yii::$service->product->getPrimaryKey();
  45. //echo $productPrimaryKey;exit;
  46. return [
  47. $productPrimaryKey,
  48. 'name',
  49. 'spu',
  50. 'sku',
  51. 'score',
  52. 'status',
  53. 'is_in_stock',
  54. 'url_key',
  55. 'price',
  56. 'cost_price',
  57. 'special_price',
  58. 'special_from',
  59. 'special_to',
  60. 'final_price', // 算出来的最终价格。这个通过脚本赋值。
  61. 'image',
  62. 'short_description',
  63. 'description',
  64. 'created_at',
  65. ];
  66. }
  67. protected $_searchLangCode;
  68. // 从配置中得到当前的搜索引擎对应的有效语言。
  69. protected function getActiveLangCode()
  70. {
  71. if (!$this->_searchLangCode) {
  72. $langArr = Yii::$app->store->get('mutil_lang');
  73. foreach ($langArr as $one) {
  74. if ($one['search_engine'] == 'mysqlSearch') {
  75. $this->_searchLangCode[] = $one['lang_code'];
  76. }
  77. }
  78. }
  79. return $this->_searchLangCode;
  80. }
  81. /**
  82. * @param $product_ids | Array ,里面的子项是MongoId类型。
  83. * 将产品表的数据同步到各个语言对应的搜索表中。
  84. */
  85. protected function actionSyncProductInfo($product_ids, $numPerPage)
  86. {
  87. $sModel = $this->_searchModel;
  88. if (is_array($product_ids) && !empty($product_ids)) {
  89. $productPrimaryKey = Yii::$service->product->getPrimaryKey();
  90. $searchModel = new $this->_searchModelName();
  91. $filter['select'] = $this->getProductSelectData();
  92. $filter['asArray'] = true;
  93. $filter['where'][] = ['in', $productPrimaryKey, $product_ids];
  94. $filter['numPerPage'] = $numPerPage;
  95. $filter['pageNum'] = 1;
  96. $coll = Yii::$service->product->coll($filter);
  97. if (is_array($coll['coll']) && !empty($coll['coll'])) {
  98. $productPrimaryKey = Yii::$service->product->getPrimaryKey();
  99. foreach ($coll['coll'] as $one) {
  100. $one['product_id'] = $one[$productPrimaryKey];
  101. $one['status'] = (int)$one['status'];
  102. $one['score'] = (int)$one['score'];
  103. $one['is_in_stock'] = (int)$one['is_in_stock'];
  104. $one['created_at'] = (int)$one['created_at'];
  105. $one['price'] = (float)$one['price'];
  106. $one['cost_price'] = (float)$one['cost_price'];
  107. $one['special_price'] = (float)$one['special_price'];
  108. $one['special_from'] = (int)$one['special_from'];
  109. $one['special_to'] = (int)$one['special_to'];
  110. $one['final_price'] = (float)$one['final_price'];
  111. unset($one[$productPrimaryKey]);
  112. //$langCodes = Yii::$service->fecshoplang->allLangCode;
  113. //if(!empty($langCodes) && is_array($langCodes)){
  114. // foreach($langCodes as $langCodeInfo){
  115. $one_name = $one['name'];
  116. $one_description = $one['description'];
  117. $one_short_description = $one['short_description'];
  118. $searchLangCode = $this->getActiveLangCode();
  119. if (!empty($searchLangCode) && is_array($searchLangCode)) {
  120. foreach ($searchLangCode as $langCode) {
  121. $one['lang'] = $langCode;
  122. $one['image'] = serialize($one['image']);
  123. $searchModel = $this->_searchModel->findOne([
  124. 'product_id' => $one['product_id'],
  125. 'lang' => $langCode,
  126. ]);
  127. if (!$searchModel['product_id']) {
  128. $searchModel = new $this->_searchModelName();
  129. }
  130. $one['name'] = Yii::$service->fecshoplang->getLangAttrVal($one_name, 'name', $langCode);
  131. $one['description'] = Yii::$service->fecshoplang->getLangAttrVal($one_description, 'description', $langCode);
  132. $one['short_description'] = Yii::$service->fecshoplang->getLangAttrVal($one_short_description, 'short_description', $langCode);
  133. $one['sync_updated_at'] = time();
  134. Yii::$service->helper->ar->save($searchModel, $one);
  135. if ($errors = Yii::$service->helper->errors->get()) {
  136. // 报错。
  137. var_dump($errors);
  138. //return false;
  139. }
  140. }
  141. }
  142. }
  143. }
  144. }
  145. //echo "MongoSearch sync done ... \n";
  146. return true;
  147. }
  148. /**
  149. * @param $nowTimeStamp | int
  150. * 批量更新过程中,被更新的产品都会更新字段sync_updated_at
  151. * 删除mysqlSearch引擎中sync_updated_at小于$nowTimeStamp的字段.
  152. */
  153. protected function actionDeleteNotActiveProduct($nowTimeStamp)
  154. {
  155. $sModel = $this->_searchModel;
  156. echo "begin delete Mongodb Search Date \n";
  157. $searchLangCode = $this->getActiveLangCode();
  158. if (!empty($searchLangCode) && is_array($searchLangCode)) {
  159. foreach ($searchLangCode as $langCode) {
  160. //$sModel::$_lang = $langCode;
  161. // 更新时间方式删除。
  162. $this->_searchModel->deleteAll([
  163. 'and',
  164. ['<', 'sync_updated_at', (int) $nowTimeStamp],
  165. ['lang' => $langCode],
  166. ]);
  167. // 不存在更新时间的直接删除掉。
  168. $this->_searchModel->deleteAll([
  169. 'sync_updated_at' => [
  170. '?exists' => false,
  171. ],
  172. ]);
  173. }
  174. }
  175. }
  176. protected function actionRemoveByProductId($product_id)
  177. {
  178. $this->_searchModel->deleteAll([
  179. 'product_id' => $product_id,
  180. ]);
  181. return true;
  182. }
  183. /**
  184. * @param $select | Array
  185. * @param $where | Array
  186. * @param $pageNum | Int
  187. * @param $numPerPage | Array
  188. * @param $product_search_max_count | Int , 搜索结果最大产品数。
  189. * 对于上面的参数和以前的$filter类似,大致和下面的类似
  190. * [
  191. * 'category_id' => 1,
  192. * 'pageNum' => 2,
  193. * 'numPerPage' => 50,
  194. * 'orderBy' => 'name',
  195. * 'where' => [
  196. * ['>','price',11],
  197. * ['<','price',22],
  198. * ],
  199. * 'select' => ['xx','yy'],
  200. * 'group' => '$spu',
  201. * ]
  202. * 得到搜索的产品列表.
  203. */
  204. protected function actionGetSearchProductColl($select, $where, $pageNum, $numPerPage, $product_search_max_count)
  205. {
  206. // 先进行sku搜索,如果有结果,说明是针对sku的搜索
  207. $enableStatus = Yii::$service->product->getEnableStatus();
  208. $searchText = $where['$text']['$search'];
  209. $productM = Yii::$service->product->getBySku($searchText);
  210. if ($productM && $enableStatus == $productM['status']) {
  211. $collection['coll'][] = $productM;
  212. $collection['count'] = 1;
  213. } else {
  214. $filter = [
  215. 'pageNum' => $pageNum,
  216. 'numPerPage' => $numPerPage,
  217. 'where' => $where,
  218. 'product_search_max_count' => $product_search_max_count,
  219. 'select' => $select,
  220. ];
  221. //var_dump($filter);exit;
  222. $collection = $this->fullTearchText($filter);
  223. }
  224. $collection['coll'] = Yii::$service->category->product->convertToCategoryInfo($collection['coll']);
  225. //var_dump($collection);
  226. return $collection;
  227. }
  228. /**
  229. * 全文搜索
  230. * $filter Example:
  231. * $filter = [
  232. * 'pageNum' => $this->getPageNum(),
  233. * 'numPerPage' => $this->getNumPerPage(),
  234. * 'where' => $this->_where,
  235. * 'product_search_max_count' => Yii::$app->controller->module->params['product_search_max_count'],
  236. * 'select' => $select,
  237. * ];
  238. * 因为mongodb的搜索涉及到计算量,因此产品过多的情况下,要设置 product_search_max_count的值。减轻服务器负担
  239. * 因为对客户来说,前10页的产品已经足矣,后面的不需要看了,限定一下产品个数,减轻服务器的压力。
  240. * 多个spu,取score最高的那个一个显示。
  241. * 按照搜索的匹配度来进行排序,没有其他排序方式.
  242. */
  243. protected function fullTearchText($filter)
  244. {
  245. $sModel = $this->_searchModel;
  246. $where = $filter['where'];
  247. $searchText = $where['$text']['$search'];
  248. unset($where['$text']);
  249. $whereArr[] = 'and';
  250. $whereArr[] = [ 'or', ['like', 'name', $searchText ], ['like', 'description', $searchText ] ];
  251. if (!isset($where['status'])) {
  252. $where['status'] = Yii::$service->product->getEnableStatus();
  253. }
  254. //$product_search_max_count = $filter['product_search_max_count'] ? $filter['product_search_max_count'] : 1000;
  255. foreach ($where as $k=>$v) {
  256. if (is_array($v)) {
  257. $k !== 'price' || $k = 'final_price';
  258. $rangBegin = isset($v['$gte']) ? $v['$gte'] : (isset($v['$gt']) ? $v['$gt'] : '');
  259. $rangEnd = isset($v['$lte']) ? $v['$lte'] : (isset($v['$lt']) ? $v['$lt'] : '');
  260. if ($rangBegin) {
  261. $whereArr[] = ['>=', $k, $rangBegin];
  262. }
  263. if ($rangEnd) {
  264. $whereArr[] = ['<', $k, $rangEnd];
  265. }
  266. } else {
  267. $whereArr[][$k] = $v;
  268. }
  269. }
  270. // lang code
  271. $whereArr[] = ['lang' => Yii::$service->store->currentLangCode];
  272. $select = $filter['select'];
  273. $pageNum = $filter['pageNum'];
  274. $numPerPage = $filter['numPerPage'];
  275. $orderBy = $filter['orderBy'];
  276. $count = 0;
  277. $searchM = $this->_searchModel->find()->asArray()->where($whereArr);
  278. if (Yii::$service->search->productSpuShowOnlyOneSku) {
  279. /**
  280. * 如果产品spu存在多个sku(譬如同一款产品存在多个颜色尺码),但是分类页只显示一个sku,那么需要通过
  281. * 下面的逻辑,对spu进行group,对score倒序,取score最大的那个sku作为分类列表显示
  282. */
  283. $orderBy['score'] = SORT_DESC;
  284. $query = $searchM->orderBy($orderBy)->groupBy('spu')->limit($numPerPage)->offset(($pageNum-1)*$numPerPage);
  285. $search_data = $query->all();
  286. $count = $query->limit(null)->offset(null)->count();
  287. } else {
  288. if ($orderBy) {
  289. $searchM->orderBy($orderBy);
  290. }
  291. $query = $searchM->limit($numPerPage)->offset(($pageNum-1)*$numPerPage);
  292. $search_data = $query->all();
  293. $count = $query->limit(null)->offset(null)->count();
  294. }
  295. $productIds = [];
  296. foreach ($search_data as $d) {
  297. $productIds[] = $d['product_id'];
  298. }
  299. // 通过productIds数组 得到产品数据
  300. $productPrimaryKey = Yii::$service->product->getPrimaryKey();
  301. if (!empty($productIds)) {
  302. foreach ($select as $sk => $se) {
  303. if ($se == 'product_id') {
  304. unset($select[$sk]);
  305. }
  306. }
  307. $select[] = $productPrimaryKey;
  308. $filter = [
  309. 'select' => $select,
  310. 'where' => [
  311. [ 'in', $productPrimaryKey, $productIds]
  312. ],
  313. ];
  314. $collData = Yii::$service->product->coll($filter);
  315. $return_data = $collData['coll'];
  316. return [
  317. 'coll' => $return_data,
  318. 'count'=> $count,
  319. ];
  320. }
  321. return [];
  322. }
  323. /**
  324. * @param $filter_attr | String 需要进行统计的字段名称
  325. * @propertuy $where | Array 搜索条件。这个需要些mongodb的搜索条件。
  326. * 得到的是个属性,以及对应的个数。
  327. * 这个功能是用于前端分类侧栏进行属性过滤。
  328. * mysql 功能受限,这个废掉了。
  329. */
  330. protected function actionGetFrontSearchFilter($filter_attr, $where)
  331. {
  332. return [];
  333. }
  334. }