ElasticsearchEngine.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. namespace App\Search\Engines;
  3. use App\Search\Builders\ElasticsearchBuilder;
  4. use Elasticsearch\Client as Elastic;
  5. use Illuminate\Database\Eloquent\Collection;
  6. use Laravel\Scout\Builder;
  7. use Laravel\Scout\Engines\Engine;
  8. use Monolog\Handler\StreamHandler;
  9. use Monolog\Logger;
  10. class ElasticsearchEngine extends Engine
  11. {
  12. private $type="doc";
  13. /**
  14. * @var Elastic
  15. */
  16. private $elastic;
  17. private $logger;
  18. /**
  19. * ElasticsearchEngine constructor.
  20. * @param Elastic $elastic
  21. * @throws \Exception
  22. */
  23. public function __construct(Elastic $elastic)
  24. {
  25. $this->elastic = $elastic;
  26. $this->logger = new Logger('elastic');
  27. $this->logger->pushHandler(new StreamHandler(storage_path('logs/elasticsearch/error.log'), Logger::ERROR));
  28. }
  29. /**
  30. * Update the given model in the index.
  31. *
  32. * @param \Illuminate\Database\Eloquent\Collection $models
  33. * @return void
  34. */
  35. public function update($models)
  36. {
  37. $params['body'] = [];
  38. $models->each(function ($model) use (&$params) {
  39. $params['body'][] = [
  40. 'update' => [
  41. '_id' => $model->getKey(),
  42. '_index' => $model->searchableAs(),
  43. '_type' => $this->type,
  44. ]
  45. ];
  46. $params['body'][] = [
  47. 'doc' => $model->toSearchableArray(),
  48. 'doc_as_upsert' => true
  49. ];
  50. });
  51. $result=$this->elastic->bulk($params);
  52. if ($result['errors'] === true) {
  53. $this->logger->error("更新失败", $result);
  54. //throw new \Exception(json_encode($result));
  55. }
  56. }
  57. /**
  58. * Remove the given model from the index.
  59. *
  60. * @param \Illuminate\Database\Eloquent\Collection $models
  61. * @return void
  62. */
  63. public function delete($models)
  64. {
  65. $params['body'] = [];
  66. $models->each(function ($model) use (&$params) {
  67. $params['body'][] = [
  68. 'delete' => [
  69. '_id' => $model->getKey(),
  70. '_index' => $model->searchableAs(),
  71. '_type' => $this->type,
  72. ]
  73. ];
  74. });
  75. $this->elastic->bulk($params);
  76. }
  77. /**
  78. * Perform the given search on the engine.
  79. *
  80. * @param \Laravel\Scout\Builder $builder
  81. * @return mixed
  82. */
  83. public function search(Builder $builder)
  84. {
  85. return $this->performSearch($builder, array_filter([
  86. 'size' => $builder->limit,
  87. ]));
  88. }
  89. /**
  90. * Perform the given search on the engine.
  91. *
  92. * @param \Laravel\Scout\Builder $builder
  93. * @param int $perPage
  94. * @param int $page
  95. * @return mixed
  96. */
  97. public function paginate(Builder $builder, $perPage, $page)
  98. {
  99. $result = $this->performSearch($builder, [
  100. 'from' => (($page * $perPage) - $perPage),
  101. 'size' => $perPage,
  102. ]);
  103. return $result;
  104. }
  105. /**
  106. * Pluck and return the primary keys of the given results.
  107. *
  108. * @param mixed $results
  109. * @return \Illuminate\Support\Collection
  110. */
  111. public function mapIds($results)
  112. {
  113. return collect($results['hits']['hits'])->pluck('_id')->values();
  114. }
  115. /**
  116. * Map the given results to instances of the given model.
  117. *
  118. * @param \Laravel\Scout\Builder $builder
  119. * @param mixed $results
  120. * @param \Illuminate\Database\Eloquent\Model $model
  121. * @return \Illuminate\Database\Eloquent\Collection
  122. */
  123. public function map(Builder $builder, $results, $model)
  124. {
  125. if ($results['hits']['total'] === 0) {
  126. return Collection::make();
  127. }
  128. $keys = collect($results['hits']['hits'])
  129. ->pluck('_id')->values()->all();
  130. $models = $model->getScoutModelsByIds($builder, $keys)->keyBy(function ($model) {
  131. return $model->getScoutKey();
  132. });
  133. return collect($results['hits']['hits'])->map(function ($hit) use ($model, $models) {
  134. return isset($models[$hit['_id']]) ? $models[$hit['_id']] : null;
  135. })->filter()->values();
  136. }
  137. /**
  138. * Get the total count from a raw result returned by the engine.
  139. *
  140. * @param mixed $results
  141. * @return int
  142. */
  143. public function getTotalCount($results)
  144. {
  145. return $results['hits']['total']>1000?1000:$results['hits']['total'];
  146. }
  147. /**
  148. * Flush all of the model's records from the engine.
  149. *
  150. * @param \Illuminate\Database\Eloquent\Model $model
  151. * @return void
  152. */
  153. public function flush($model)
  154. {
  155. // TODO: Implement flush() method.
  156. }
  157. /**
  158. * Perform the given search on the engine.
  159. *
  160. * @param ElasticsearchBuilder $builder
  161. * @param array $options
  162. * @return mixed
  163. */
  164. protected function performSearch($builder, array $options = [])
  165. {
  166. $params = [
  167. 'index' => $builder->model->searchableAs(),
  168. 'type' => $this->type,
  169. 'body' => [
  170. 'query' => [
  171. 'bool' => [
  172. ]
  173. ]
  174. ]
  175. ];
  176. if (!empty($query = $builder->getQueryData())) {
  177. $params['body']['query']['bool']=$query;
  178. }
  179. $relation_sotr=[];
  180. if (!empty($builder->query)) {
  181. $relation_sotr[]=['_score' => 'desc'];
  182. $params['body']['query']['bool']['must'][]=[
  183. 'bool'=>[
  184. "should"=>[
  185. [
  186. 'match' => [
  187. 'search_field1' => [
  188. "query" => "{$builder->query}",
  189. "boost" => 2
  190. ]
  191. ]
  192. ],
  193. [
  194. 'match' => [
  195. 'search_field2' => [
  196. "query" => "{$builder->query}",
  197. "boost" => 0
  198. ]
  199. ]
  200. ]
  201. ]
  202. ]
  203. ];
  204. }
  205. if (empty($params['body']['query']['bool'])) {
  206. $params['body']['query']['bool']['must'][]=['match_all'=>new \ArrayObject()];
  207. }
  208. if ($sort = $this->sort($builder)) {
  209. if (!empty($relation_sotr)) {
  210. $sort =array_merge($relation_sotr, $sort);
  211. }
  212. $params['body']['sort'] = $sort;
  213. }
  214. if (isset($options['from'])) {
  215. $params['body']['from'] = $options['from'];
  216. }
  217. if (isset($options['size'])) {
  218. $params['body']['size'] = $options['size'];
  219. }
  220. if ($builder->callback) {
  221. return call_user_func(
  222. $builder->callback,
  223. $this->elastic,
  224. $builder->query,
  225. $params
  226. );
  227. }
  228. return $this->elastic->search($params);
  229. }
  230. /**
  231. * Generates the sort if theres any.
  232. *
  233. * @param Builder $builder
  234. * @return array|null
  235. */
  236. protected function sort($builder)
  237. {
  238. if (count($builder->orders) == 0) {
  239. return null;
  240. }
  241. return collect($builder->orders)->map(function ($order) {
  242. if (is_array($order['column'])) {
  243. return ['_geo_distance'=> $order['column']];
  244. }
  245. return [$order['column'] => $order['direction']];
  246. })->toArray();
  247. }
  248. }