├── .gitignore
├── CHANGELOG.md
├── src
├── Exception
│ ├── OpensearchCallException.php
│ ├── OpensearchRunException.php
│ └── OpensearchException.php
├── Events
│ ├── ModelsDeleted.php
│ ├── ModelsUpdated.php
│ └── DocSyncEvent.php
├── Sdk
│ ├── readme.txt
│ ├── License
│ ├── CloudsearchSuggest.php
│ ├── CloudsearchIndex.php
│ ├── CloudsearchDoc.php
│ ├── CloudsearchClient.php
│ └── CloudsearchSearch.php
├── SearchableMethods.php
├── Helper
│ └── Whenable.php
├── OpenSearchServiceProvider.php
├── Jobs
│ ├── MakeSearchable.php
│ ├── RemoveSearchable.php
│ └── UpdateSearchable.php
├── Console
│ └── FlushCommand.php
├── SearchableScope.php
├── Query
│ ├── Grammar.php
│ ├── QueryStructureBuilder.php
│ └── Builder.php
├── OpenSearchClient.php
├── Searchable.php
├── ExtendedBuilder.php
└── OpenSearchEngine.php
├── tests
└── SearchTest.php
├── composer.json
├── phpunit.xml
├── LICENSE
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | /vendor
2 | /.phpintel
3 | /.idea
4 | /test.php
5 | composer.lock
6 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | ## v0.0.3 (2017-05-02)
4 |
5 | ### Added
6 | - 添加 `orderByRank` 方法,按排序分排序
7 |
--------------------------------------------------------------------------------
/src/Exception/OpensearchCallException.php:
--------------------------------------------------------------------------------
1 | errors = $errors;
14 | }
15 |
16 | public function getErrors()
17 | {
18 | return $this->errors;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/SearchableMethods.php:
--------------------------------------------------------------------------------
1 | queueMakeSearchable(new Collection([$this]));
12 | }
13 |
14 | public function updateSearchable()
15 | {
16 | $this->queueUpdateSearchable(new Collection([$this]));
17 | }
18 |
19 | public function removeSearchable()
20 | {
21 | $this->queueRemoveFromSearch(new Collection([$this]));
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/tests/SearchTest.php:
--------------------------------------------------------------------------------
1 | opensearchClient = new OpenSearchClient([
12 | 'access_key_id' => '',
13 | 'access_key_secret' => '',
14 | 'debug' => true,
15 | ]);
16 | }
17 |
18 | public function test_something_work()
19 | {
20 | $this->assertTrue(true);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/Events/DocSyncEvent.php:
--------------------------------------------------------------------------------
1 | data = $data;
22 | $this->type = $type;
23 | $this->success = $success;
24 | $this->message = $message;
25 | $this->indexName = $indexName;
26 | $this->tableName = $tableName;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Sdk/License:
--------------------------------------------------------------------------------
1 | Copyright 1999-2015 Alibaba Group Holding Ltd.
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/src/Helper/Whenable.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
42 | * $suggest = new CloudsearchSuggest($client);
43 | *
44 | * $opts = array(
45 | * "index_name" => "index_name",
46 | * "suggest_name" => "suggest_name",
47 | * "hit" => 10,
48 | * "query" => "query"
49 | * );
50 | *
51 | * echo $suggest->search($opts);
52 | *
53 | *
54 | */
55 | class CloudsearchSuggest
56 | {
57 | private $client = null;
58 |
59 | private $indexName = null;
60 |
61 | private $suggestName = null;
62 |
63 | private $hits = 10;
64 |
65 | private $query = null;
66 |
67 | private $path = "/suggest";
68 |
69 | public function __construct($client)
70 | {
71 | $this->client = $client;
72 | }
73 |
74 | /**
75 | * 设定下拉提示对应的应用名称
76 | *
77 | * @param string $indexName 指定的应用名称
78 | */
79 | public function setIndexName($indexName)
80 | {
81 | $this->indexName = $indexName;
82 | }
83 |
84 | /**
85 | * 获取下拉提示对应的应用名称
86 | *
87 | * @return string 返回应用名称
88 | */
89 | public function getIndexName()
90 | {
91 | return $this->indexName;
92 | }
93 |
94 | /**
95 | * 设定下拉提示名称
96 | *
97 | * @param string $suggestName 指定的下拉提示名称。
98 | */
99 | public function setSuggestName($suggestName)
100 | {
101 | $this->suggestName = $suggestName;
102 | }
103 |
104 | /**
105 | * 获取下拉提示名称
106 | *
107 | * @return string 返回下拉提示名称。
108 | */
109 | public function getSuggestName()
110 | {
111 | return $this->suggestName;
112 | }
113 |
114 | /**
115 | * 设定返回结果条数
116 | *
117 | * @param int $hits 返回结果的条数。
118 | */
119 | public function setHits($hits)
120 | {
121 | $hits = (int) $hits;
122 | if ($hits < 0) {
123 | $hits = 0;
124 | }
125 | $this->hits = $hits;
126 | }
127 |
128 | /**
129 | * 获取返回结果条数
130 | *
131 | * @return int 返回条数。
132 | */
133 | public function getHits()
134 | {
135 | return $this->hits;
136 | }
137 |
138 | /**
139 | * 设定要查询的关键词
140 | *
141 | * @param string $query 要查询的关键词。
142 | */
143 | public function setQuery($query)
144 | {
145 | $this->query = $query;
146 | }
147 |
148 | /**
149 | * 获取要查询的关键词
150 | *
151 | * @return string 返回要查询的关键词。
152 | */
153 | public function getQuery()
154 | {
155 | return $this->query;
156 | }
157 |
158 | /**
159 | * 发出查询请求
160 | *
161 | * @param array $opts options参数列表
162 | * @subparam index_name 应用名称
163 | * @subparam suggest_name 下拉提示名称
164 | * @subparam hits 返回结果条数
165 | * @subparam query 查询关键词
166 | * @return string 返回api返回的结果。
167 | */
168 | public function search($opts = array())
169 | {
170 | if (!empty($opts)) {
171 | if (isset($opts['index_name']) && $opts['index_name'] !== '') {
172 | $this->setIndexName($opts['index_name']);
173 | }
174 |
175 | if (isset($opts['suggest_name']) && $opts['suggest_name'] !== '') {
176 | $this->setSuggestName($opts['suggest_name']);
177 | }
178 |
179 | if (isset($opts['hits']) && $opts['hits'] !== '') {
180 | $this->setHits($opts['hits']);
181 | }
182 |
183 | if (isset($opts['query']) && $opts['query'] !== '') {
184 | $this->setQuery($opts['query']);
185 | }
186 | }
187 |
188 | $params = array(
189 | "index_name" => $this->getIndexName(),
190 | "suggest_name" => $this->getSuggestName(),
191 | "hit" => $this->getHits(),
192 | "query" => $this->getQuery(),
193 | );
194 |
195 | return $this->client->call($this->path, $params);
196 | }
197 | }
198 |
--------------------------------------------------------------------------------
/src/Sdk/CloudsearchIndex.php:
--------------------------------------------------------------------------------
1 | client = $client;
59 | $this->indexName = $indexName;
60 | $this->_setPath($indexName);
61 | }
62 |
63 | /**
64 | * 通过模板名称创建应用
65 | *
66 | * 用指定的模板名称创建一个新的应用。
67 | * @param string $templateName 模板名称(可以使系统内置模板,也可以是自定义模板)
68 | * @param array $opts 包含应用的备注信息。
69 | *
70 | * @return string 返回api返回的结果。
71 | */
72 | public function createByTemplateName($templateName, $opts = array())
73 | {
74 | $params = array(
75 | 'action' => "create",
76 | 'template' => $templateName,
77 | );
78 |
79 | if (isset($opts['desc']) && !empty($opts['desc'])) {
80 | $params['index_des'] = $opts['desc'];
81 | }
82 |
83 | return $this->client->call($this->path, $params);
84 | }
85 |
86 | /**
87 | * 通过模板创建应用
88 | *
89 | * 用指定的模板创建一个新的应用。模版是一个格式化数组,用于描述应用的结构,可以在控制台中通过创建应用->保存模板->导出模板来获得json结构的模板;也可以自己生成,格式见控制台模板管理。
90 | * @param string $template 使用的模板
91 | * @param array $opts 包含应用的备注信息。
92 | *
93 | * @return string 返回api返回的正确或错误的结果。
94 | */
95 | public function createByTemplate($template, $opts = array())
96 | {
97 | $params = array(
98 | 'action' => "create",
99 | 'template' => $template,
100 | );
101 |
102 | if (isset($opts['desc']) && !empty($opts['desc'])) {
103 | $params['index_des'] = $opts['desc'];
104 | }
105 |
106 | $params['template_type'] = 2;
107 |
108 | return $this->client->call($this->path, $params);
109 | }
110 |
111 | /**
112 | * 修改应用名称和备注
113 | *
114 | * 更新当前应用的应用名称和备注信息。
115 | * @param string $toIndexName 更改后的新名字
116 | * @param array $opts 可选参数,包含: desc 应用备注信息
117 | * @return string API返回的操作结果
118 | */
119 | public function rename($toIndexName, $opts = array())
120 | {
121 | $params = array(
122 | 'action' => "update",
123 | 'new_index_name' => $toIndexName,
124 | );
125 |
126 | if (isset($opts['desc']) && !empty($opts['desc'])) {
127 | $params['description'] = $opts['desc'];
128 | }
129 |
130 | $result = $this->client->call($this->path, $params);
131 | $json = json_decode($result, true);
132 | if (isset($json['status']) && $json['status'] == 'OK') {
133 | $this->indexName = $toIndexName;
134 | $this->_setPath($toIndexName);
135 | }
136 | return $result;
137 | }
138 |
139 | private function _setPath($indexName)
140 | {
141 | $this->path = '/index/' . $indexName;
142 | }
143 |
144 | /**
145 | * 删除应用
146 | *
147 | * @return string API返回的操作结果
148 | */
149 | public function delete()
150 | {
151 | return $this->client->call($this->path, array('action' => "delete"));
152 | }
153 |
154 | /**
155 | * 查看应用状态
156 | *
157 | * @return string API返回的操作结果
158 | */
159 | public function status()
160 | {
161 | return $this->client->call($this->path, array('action' => "status"));
162 | }
163 |
164 | /**
165 | * 列出所有应用
166 | *
167 | * @param int $page 页码
168 | * @param int $pageSize 每页的记录条数
169 | */
170 | public function listIndexes($page = 1, $pageSize = 10)
171 | {
172 | $params = array(
173 | 'page' => $page,
174 | 'page_size' => $pageSize,
175 | );
176 | return $this->client->call('/index', $params);
177 | }
178 |
179 | /**
180 | * 获取应用名称
181 | *
182 | * 获取当前应用的名称。
183 | *
184 | * @return string 当前应用的名称
185 | */
186 | public function getIndexName()
187 | {
188 | return $this->indexName;
189 | }
190 |
191 | /**
192 | * 获取应用的最近错误列表
193 | *
194 | * @param int $page 指定获取第几页的错误信息。默认值:1
195 | * @param int $pageSize 指定每页显示的错误条数。默认值:10
196 | *
197 | * @return array 返回指定页数的错误信息列表。
198 | */
199 | public function getErrorMessage($page = 1, $pageSize = 10)
200 | {
201 | $this->_checkPageClause($page);
202 | $this->_checkPageSizeClause($pageSize);
203 |
204 | $params = array(
205 | 'page' => $page,
206 | 'page_size' => $pageSize,
207 | );
208 | return $this->client->call('/index/error/' . $this->indexName, $params);
209 | }
210 |
211 | /**
212 | * 检查$page参数是否合法。
213 | *
214 | * @param int $page 指定的页码。
215 | *
216 | * @throws Exception 如果参数不正确,则抛出此异常。
217 | *
218 | * @access private
219 | */
220 | private function _checkPageClause($page)
221 | {
222 | if (null == $page || !is_int($page)) {
223 | throw new Exception('$page is not an integer.');
224 | }
225 | if ($page <= 0) {
226 | throw new Exception('$page is not greater than or equal to 0.');
227 | }
228 | }
229 |
230 | /**
231 | * 检查$pageSize参数是否合法。
232 | *
233 | * @param int $pageSize 每页显示的记录条数。
234 | *
235 | * @throws Exception 参数不合法
236 | *
237 | * @access private
238 | */
239 | private function _checkPageSizeClause($pageSize)
240 | {
241 | if (null == $pageSize || !is_int($pageSize)) {
242 | throw new Exception('$pageSize is not an integer.');
243 | }
244 | if ($pageSize <= 0) {
245 | throw new Exception('$pageSize is not greater than 0.');
246 | }
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/ExtendedBuilder.php:
--------------------------------------------------------------------------------
1 | model = $model;
124 | $this->query = $query;
125 | $this->callback = $callback;
126 |
127 | $this->select();
128 | }
129 |
130 | /**
131 | * Specify a custom index to perform this search on.
132 | *
133 | * @param string $index
134 | * @return $this
135 | */
136 | public function within($index)
137 | {
138 | $this->index = $index;
139 |
140 | return $this;
141 | }
142 |
143 | /**
144 | * Add a constraint to the search filter.
145 | *
146 | * @param mixed $field
147 | * @param mixed $value
148 | * @return $this
149 | */
150 | public function filter($field, $value = null)
151 | {
152 | if (is_array($field)) {
153 | $this->filters[] = $field;
154 | } else {
155 | if (! is_array($value)) {
156 | $value = [$field, '=', $value];
157 | } else {
158 | array_unshift($value, $field);
159 | }
160 |
161 | $this->filters[] = $value;
162 | }
163 |
164 | return $this;
165 | }
166 |
167 | /**
168 | * Set the "limit" for the search query.
169 | *
170 | * @param int $limit
171 | * @return $this
172 | */
173 | public function take($limit)
174 | {
175 | $this->limit = $limit;
176 |
177 | return $this;
178 | }
179 |
180 | public function forPage($page, $perPage = 20)
181 | {
182 | $this->page = $page;
183 | $this->limit = $perPage;
184 |
185 | return $this;
186 | }
187 |
188 | /**
189 | * Add a constraint to the search query.
190 | *
191 | * @param string $field
192 | * @param array $values
193 | * @return $this
194 | */
195 | public function filterIn($field, array $values = [])
196 | {
197 | $this->rawFilters[] = '(' . collect($values)->map(function($item) use ($field) {
198 | $item = !is_numeric($item) && is_string($item) ? '"' . $item . '"' : $item;
199 | return $field . '=' . $item;
200 | })->implode(' OR ') . ')';
201 |
202 | return $this;
203 | }
204 |
205 | /**
206 | * Add an "order" for the search query.
207 | *
208 | * @param string $column
209 | * @param string $direction
210 | * @return $this
211 | */
212 | public function orderBy($column, $direction = 'asc')
213 | {
214 | $this->orders[] = [
215 | 'column' => $column,
216 | 'direction' => strtolower($direction) == 'asc' ? 'asc' : 'desc',
217 | ];
218 |
219 | return $this;
220 | }
221 |
222 | /**
223 | * Add an default rank to order.
224 | *
225 | * @param string $direction
226 | * @return $this
227 | */
228 | public function orderByRank($direction = 'desc')
229 | {
230 | $this->orders[] = [
231 | 'column' => 'RANK',
232 | 'direction' => strtolower($direction) == 'desc' ? 'desc' : 'asc',
233 | ];
234 |
235 | return $this;
236 | }
237 |
238 | public function select($fields = null)
239 | {
240 | if (empty($fields)) {
241 | $fields = $this->model->getSearchableFields();
242 |
243 | if (! is_array($fields)) {
244 | $fields = explode(',', $fields);
245 | }
246 | }
247 |
248 | $this->fields = $fields;
249 |
250 | return $this;
251 | }
252 |
253 | public function filterRaw($rawFilter)
254 | {
255 | $this->rawFilters[] = $rawFilter;
256 |
257 | return $this;
258 | }
259 |
260 | public function searchRaw($rawQuery)
261 | {
262 | $this->rawQuerys[] = $rawQuery;
263 |
264 | return $this;
265 | }
266 |
267 | public function addDistinct()
268 | {
269 | $this->distincts[] = func_get_args();
270 |
271 | return $this;
272 | }
273 |
274 | public function addAggregate()
275 | {
276 | $this->aggregates[] = func_get_args();
277 |
278 | return $this;
279 | }
280 |
281 | public function setPair($pair)
282 | {
283 | $this->pair = $pair;
284 |
285 | return $this;
286 | }
287 |
288 | /**
289 | * Get the keys of search results.
290 | *
291 | * @return \Illuminate\Support\Collection
292 | */
293 | public function keys()
294 | {
295 | return $this->engine()->keys($this);
296 | }
297 |
298 | /**
299 | * Get the first result from the search.
300 | *
301 | * @return \Illuminate\Database\Eloquent\Model
302 | */
303 | public function first()
304 | {
305 | return $this->get()->first();
306 | }
307 |
308 | /**
309 | * Get the results of the search.
310 | *
311 | * @return \Illuminate\Database\Eloquent\Collection
312 | */
313 | public function get()
314 | {
315 | return $this->engine()->get($this);
316 | }
317 |
318 | /**
319 | * Get the facet from aggregate.
320 | *
321 | * @return \Illuminate\Database\Eloquent\Collection
322 | */
323 | public function facet($key)
324 | {
325 | return $this->engine()->facet($key, $this);
326 | }
327 |
328 | /**
329 | * Paginate the given query into a simple paginator.
330 | *
331 | * @param int $perPage
332 | * @param string $pageName
333 | * @param int|null $page
334 | * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
335 | */
336 | public function paginate($perPage = null, $pageName = 'page', $page = null)
337 | {
338 | $engine = $this->engine();
339 |
340 | $page = $page ?: Paginator::resolveCurrentPage($pageName);
341 |
342 | $perPage = $perPage ?: $this->model->getPerPage();
343 |
344 | $this->forPage($page, $perPage);
345 |
346 | $results = Collection::make($engine->map(
347 | $rawResults = $engine->paginate($this, $perPage, $page), $this->model
348 | ));
349 |
350 | $paginator = (new LengthAwarePaginator($results, $engine->getTotalCount($rawResults), $perPage, $page, [
351 | 'path' => Paginator::resolveCurrentPath(),
352 | 'pageName' => $pageName,
353 | ]));
354 |
355 | return $paginator->appends('query', $this->query);
356 | }
357 |
358 | /**
359 | * Get the engine that should handle the query.
360 | *
361 | * @return mixed
362 | */
363 | protected function engine()
364 | {
365 | return $this->model->searchableUsing();
366 | }
367 | }
368 |
--------------------------------------------------------------------------------
/src/OpenSearchEngine.php:
--------------------------------------------------------------------------------
1 | opensearch = $opensearch;
46 |
47 | $this->cloudsearchSearch = $opensearch->getCloudSearchSearch();
48 | }
49 |
50 | /**
51 | * Add the given model in the index.
52 | *
53 | * @param \Illuminate\Database\Eloquent\Collection $models
54 | * @return void
55 | */
56 | public function add($models)
57 | {
58 | // Get opensearch index client.
59 | $doc = $this->getCloudSearchDoc($models);
60 |
61 | foreach ($this->getSearchableData($models, ['update']) as $name => $value) {
62 | if (! empty($value['update'])) {
63 | try {
64 | $this->waitASecond();
65 | $doc->add($value['update'], $name);
66 | $this->waitASecond();
67 |
68 | Event::fire(new DocSyncEvent($models->first()->searchableAs(), $name, $value, 'add', true));
69 | } catch (OpensearchException $e) {
70 | throw $e;
71 | }
72 | }
73 | }
74 | }
75 |
76 | /**
77 | * Update the given model in the index.
78 | *
79 | * @param \Illuminate\Database\Eloquent\Collection $models
80 | * @return void
81 | */
82 | public function update($models)
83 | {
84 | // Get opensearch index client.
85 | $doc = $this->getCloudSearchDoc($models);
86 |
87 | foreach ($this->getSearchableData($models) as $name => $value) {
88 | foreach ($value as $method => $items) {
89 | if (! empty($items)) {
90 | try {
91 | $this->waitASecond();
92 | $doc->$method($items, $name);
93 | $this->waitASecond();
94 |
95 | Event::fire(new DocSyncEvent($models->first()->searchableAs(), $name, $value, $method, true));
96 | } catch (OpensearchException $e) {
97 | throw $e;
98 | }
99 | }
100 | }
101 | }
102 | }
103 |
104 | /**
105 | * Remove the given model from the index.
106 | *
107 | * @param \Illuminate\Database\Eloquent\Collection $models
108 | * @return void
109 | */
110 | public function delete($models)
111 | {
112 | $doc = $this->getCloudSearchDoc($models);
113 |
114 | /*
115 | |----------------------------------------------------------------------
116 | | 有删除逻辑的走删除逻辑,没有删除逻辑的直接走 id 删除
117 | |----------------------------------------------------------------------
118 | |
119 | | 同时, opensearch 中的应用多表结构,适用于水平分库,或者提取字段专门建立的福鼠表
120 | | 所以对于数据的删除,只是直接删除 id.
121 | |
122 | */
123 | foreach ($this->getSearchableData($models, ['delete']) as $name => $value) {
124 | if (array_key_exists('delete', $value)) {
125 | $toBeDeleteData = $value['delete'];
126 | } else {
127 | $toBeDeleteData = $models->map(function ($model) {
128 | return [
129 | 'cmd' => 'delete',
130 | 'fields' => [
131 | 'id' => $model->id,
132 | ]
133 | ];
134 | });
135 | }
136 |
137 | if (! empty($toBeDeleteData)) {
138 | try {
139 | $this->waitASecond();
140 | $doc->delete($toBeDeleteData, $name);
141 | $this->waitASecond();
142 |
143 | Event::fire(new DocSyncEvent($models->first()->searchableAs(), $name, $value, 'delete', true));
144 | } catch (OpensearchException $e) {
145 | throw $e;
146 | }
147 | }
148 | }
149 | }
150 |
151 | /**
152 | * Sleep 100ms to avoid request frequently.
153 | *
154 | * 经过测试 200ms 比较稳定, 在请求前后分别停止 100ms
155 | *
156 | * @param integer $microSeconds
157 | * @return null
158 | */
159 | protected function waitASecond($microSeconds = 100000)
160 | {
161 | usleep($microSeconds);
162 | }
163 |
164 | /**
165 | * Equals remove
166 | */
167 | public function remove($models)
168 | {
169 | return $this->delete($models);
170 | }
171 |
172 | /**
173 | * 获取模型的操作数据
174 | *
175 | * @param \Illuminate\Database\Eloquent\Collection $models
176 | * @param array $actions
177 | * @return array
178 | */
179 | protected function getSearchableData($models, array $actions = ['delete', 'update'])
180 | {
181 | // 获取应用的全部表名
182 | $tableNames = array_keys($models->first()->toSearchableDocCallbacks());
183 | /*
184 | |--------------------------------------------------------------------------
185 | | 构造数据,分两块
186 | |--------------------------------------------------------------------------
187 | |
188 | | 在 opensearch 中,添加(add)和更新(update)是一样的操作,都是存在即更新,不存在则创建,且作为客户端我们不需要知道这条文档是被添加了或删除了
189 | | 那么最稳健的做法是,如果数据 id 是数据主键(或者其他稳定数据),那么只需要在直接更新就可以了
190 | | 碰到自己构造的 id 情况,就需要手动处理找出数据源 id 之后,手动删除了
191 | |
192 | | 插入数据,一般来说是第一次向 opensearch 添加数据时会使用, 所以,过滤掉 delete 操作
193 | |
194 | */
195 | $data = [];
196 | foreach ($tableNames as $name) {
197 | $data[$name] = [];
198 | }
199 |
200 | foreach ($models as $model) {
201 | $callbacks = $model->toSearchableDocCallbacks($actions);
202 |
203 | // delete 就是需要在 update 前面
204 | ksort($callbacks);
205 |
206 | foreach ($callbacks as $name => $callback) {
207 | if (! empty($callback)) {
208 | foreach ($actions as $action) {
209 | if (isset($callback[$action])) {
210 | $data[$name][$action] = array_merge(
211 | isset($data[$name][$action]) ? $data[$name][$action] : [],
212 | call_user_func($callback[$action])
213 | );
214 | }
215 | }
216 | }
217 | }
218 | }
219 |
220 | return $data;
221 | }
222 |
223 | /**
224 | * Perform the given search on the engine.
225 | *
226 | * @param \Laravel\Scout\Builder $builder
227 | * @return mixed
228 | */
229 | public function search(ScoutBuilder $builder)
230 | {
231 | $searchKey = serialize($builder);
232 |
233 | if (! isset($this->searchResult[$searchKey])) {
234 | $this->searchResult[$searchKey] = $this->performSearch($this->buildLaravelBuilderIntoOpensearch($builder));
235 | }
236 |
237 | return $this->searchResult[$searchKey];
238 | }
239 |
240 | public function paginate(ScoutBuilder $builder, $perPage, $page)
241 | {
242 | $cloudSearchSearch = $this->buildLaravelBuilderIntoOpensearch($builder);
243 |
244 | return $this->performSearch($cloudSearchSearch);
245 | }
246 |
247 | protected function buildLaravelBuilderIntoOpensearch($builder)
248 | {
249 | return (new Builder($this->cloudsearchSearch))->build($builder);
250 | }
251 |
252 | protected function performSearch(CloudsearchSearch $search)
253 | {
254 | return $search->search();
255 | }
256 |
257 | /**
258 | * Map the given results to instances of the given model.
259 | *
260 | * @param mixed $results
261 | * @param \Illuminate\Database\Eloquent\Model $model
262 | * @return \Illuminate\Support\Collection
263 | * @throws OpensearchException
264 | */
265 | public function map($results, $model)
266 | {
267 | $fields = $this->opensearch->getCloudSearchSearch()->getFetchFields();
268 |
269 | if (empty($fields)) {
270 | throw new OpensearchException('搜索字段不能为空');
271 | }
272 |
273 | if (count($fields) != 1) {
274 | return collect(array_map(function ($item) use ($fields) {
275 | $result = [];
276 | foreach ($fields as $field) {
277 | $result[$field] = $item[$field];
278 | }
279 | return $result;
280 | }, $results['result']['items']));
281 | } else {
282 | $fields = $fields[0];
283 | }
284 |
285 | return $this->mapIds($results, $fields);
286 | }
287 |
288 | /**
289 | * Pluck and return the primary keys of the given results.
290 | *
291 | * @param mixed $results
292 | * @param string $field
293 | * @return \Illuminate\Support\Collection
294 | */
295 | public function mapIds($results, $field = 'id')
296 | {
297 | return collect($results['result']['items'])->pluck($field)->values();
298 | }
299 |
300 | /**
301 | * Get the total count from a raw result returned by the engine.
302 | *
303 | * @param mixed $results
304 | * @return int
305 | */
306 | public function getTotalCount($results)
307 | {
308 | return $results['result']['total'];
309 | }
310 |
311 | /**
312 | * @param $models
313 | * @return Sdk\CloudsearchDoc
314 | */
315 | private function getCloudSearchDoc($models)
316 | {
317 | return $this->opensearch->getCloudSearchDoc($models->first()->searchableAs());
318 | }
319 |
320 | /**
321 | * Get the results of the given query mapped onto models.
322 | *
323 | * @param \Lingxi\AliOpenSearch\ScoutBuilder $builder
324 | * @return \Illuminate\Database\Eloquent\Collection
325 | */
326 | public function get(ScoutBuilder $builder)
327 | {
328 | return Collection::make($this->map(
329 | $this->search($builder), $builder->model
330 | ));
331 | }
332 |
333 | /**
334 | * Get the facet from search results.
335 | *
336 | * @param string $key
337 | * @param ExtendedBuilder $builder
338 | * @return \Illuminate\Database\Eloquent\Collection
339 | */
340 | public function facet($key, ExtendedBuilder $builder)
341 | {
342 | return Collection::make($this->mapFacet($key, $this->search($builder)));
343 | }
344 |
345 | protected function mapFacet($key, $results)
346 | {
347 | $facets = $results['result']['facet'];
348 |
349 | foreach ($facets as $facet) {
350 | if ($facet['key'] == $key) {
351 | return $facet['items'];
352 | }
353 | }
354 |
355 | return [];
356 | }
357 | }
358 |
--------------------------------------------------------------------------------
/src/Sdk/CloudsearchDoc.php:
--------------------------------------------------------------------------------
1 | indexName = $indexName;
120 | $this->client = $client;
121 | $this->path = '/index/doc/' . $this->indexName;
122 | }
123 |
124 | /**
125 | * 查看文档
126 | *
127 | * 根据文档id获取doc的详细信息。
128 | *
129 | * @param string $docId 指定的文档id。
130 | * @return string 该docId对应的doc详细信息
131 | */
132 | public function detail($docId)
133 | {
134 | $item = array("id" => $docId);
135 | return $this->client->call($this->path, $item, self::METHOD);
136 | }
137 |
138 | /**
139 | * 更新文档
140 | *
141 | * 向指定的表中更新doc。
142 | * @param array $docs 指定要更新的doc。
143 | * @param string $tableName 指定向哪个表中更新doc。
144 | * @return string 返回API返回的结果。
145 | */
146 | public function update($docs, $tableName)
147 | {
148 | return $this->action($docs, $tableName, self::SIGN_MODE);
149 | }
150 |
151 | /**
152 | * 添加文档
153 | *
154 | * 向指定的表中增加doc。
155 | * @param array $docs 指定要添加的doc。
156 | * @param string $tableName 指定向哪个表中增加doc。
157 | * @return string 返回API返回的结果。
158 | */
159 | public function add($docs, $tableName)
160 | {
161 | return $this->action($docs, $tableName, self::SIGN_MODE);
162 | }
163 |
164 | /**
165 | * 删除文档
166 | *
167 | * 删除指定表中的doc。
168 | * @param array $docs 指定要删除的doc列表,必须含有主键。
169 | * @param string $tableName 指定要从哪个表删除记录。
170 | * @return string 返回API返回的结果。
171 | */
172 | public function delete($docs, $tableName)
173 | {
174 | return $this->action($docs, $tableName, self::SIGN_MODE);
175 | }
176 |
177 | /**
178 | * 删除文档
179 | *
180 | * 删除指定表中的doc。
181 | * @param array $docs 指定要删除的doc列表,必须含有主键。
182 | * @param string $tableName 指定要从哪个表删除记录。
183 | * @return string 返回API返回的结果。
184 | */
185 | public function remove($docs, $tableName)
186 | {
187 | return $this->action($docs, $tableName, self::SIGN_MODE);
188 | }
189 |
190 | /**
191 | * 执行文档相关操作
192 | *
193 | * @param array|string $docs 此docs为用户push的数据,此字段为json_encode的字符串或者数据。
194 | * @param string $tableName 操作的表名。
195 | * @throws Exception
196 | * @return string 请求API并返回相应的结果。
197 | */
198 | private function action($docs, $tableName, $signMode = self::SIGN_MODE)
199 | {
200 | if (!is_array($docs)) {
201 | $docs = json_decode($docs, true);
202 | }
203 |
204 | if (!is_array($docs) || empty($docs) || !is_array($docs[0])) {
205 | throw new Exception('Operation failed. The docs is not correct.');
206 | }
207 |
208 | $params = array(
209 | 'action' => "push",
210 | 'items' => json_encode($docs),
211 | 'table_name' => $tableName,
212 | );
213 |
214 | if ($signMode == self::SIGN_MODE) {;
215 | $params['sign_mode'] = self::SIGN_MODE;
216 | }
217 |
218 | return $this->client->call($this->path, $params, self::METHOD);
219 | }
220 |
221 | /**
222 | * 重新生成doc文档。
223 | * @param array $docs doc文档
224 | * @param string $type 操作类型,有ADD、UPDATE、REMOVE。
225 | * @return array 返回重新生成的doc文档。
226 | */
227 | private function generate($docs, $type)
228 | {
229 | $result = array();
230 | foreach ($docs as $doc) {
231 | $item = array('cmd' => $type);
232 | $item['fields'] = $doc;
233 | $result[] = $item;
234 | }
235 |
236 | return $result;
237 | }
238 |
239 | /**
240 | * 通过csv格式文件上传文档数据
241 | *
242 | * NOTE: 此文件必需为csv格式的文件(“,”分割);且第一行为数据结构字段名称,例如:
243 | *
244 | * id, title, name, date,1, "我的测试数据\"1\"测试1", test_name1, "2013-09-21 00:12:22"
245 | * ...
246 | *
247 | * @param string $fileName 本地文件。
248 | * @param string $primaryKey 指定此表的主键。
249 | * @param string $tableName 指定表名。
250 | * @param array $multiValue 指定此表中的多值的字段。默认值为空
251 | * @param int $offset 指定从第offset条记录开始导入。默认值为1
252 | * @param number $maxSize 指定每次push数据的最大值,单位为MB。默认值为4
253 | * @param int $frequence 指定上传数据的频率,默认值为4,单位为次/秒
254 | *
255 | * @return string 返回如果成功上传或上传失败的状态。
256 | */
257 | public function pushCSVFile($fileName, $primaryKey, $tableName,
258 | $multiValue = array(), $offset = 1, $maxSize = self::PUSH_MAX_SIZE,
259 | $frequence = self::PUSH_FREQUENCE) {
260 | $reader = $this->_connect($fileName);
261 |
262 | $lineNo = 0;
263 | $buffter = array();
264 | $latestLine = $offset - 1;
265 | $latestPrimaryKey = '';
266 | $totalSize = 0;
267 | $primaryKeyPos = 0;
268 |
269 | $time = time();
270 | $timeFreq = 0;
271 |
272 | while ($data = fgetcsv($reader, 1024, self::CSV_SEPARATOR)) {
273 | if ($lineNo == 0) {
274 | $header = $data;
275 | if (count(array_flip($data)) != count($data)) {
276 | throw new Exception('There are some multi fields in your header.');
277 | }
278 |
279 | $primaryKeyPos = array_search($primaryKey, $header);
280 | if (false === $primaryKey) {
281 | throw new Exception("The primary key '{$primaryKey}' is not exists.");
282 | }
283 | } else {
284 | if ($lineNo < $offset) {
285 | continue;
286 | }
287 |
288 | if (count($data) != count($header)) {
289 | throw new Exception("The number of columns of values is not matched
290 | the number of header of primary key '{$data[$primaryKeyPos]}'.
291 | Latest successful posted primary key number is '{$latestPrimaryKey}'.");
292 | }
293 |
294 | $item = array();
295 | $item['cmd'] = self::DOC_ADD;
296 | if (!empty($multiValue)) {
297 | foreach ($multiValue as $field => $separator) {
298 | $pos = array_search($field, $header);
299 | if ($pos !== false) {
300 | $data[$pos] = explode($separator, $data[$pos]);
301 | }
302 | }
303 | }
304 | $item['fields'] = array_combine($header, $data);
305 |
306 | $json = json_encode($item);
307 | // 检测是否push数据push成功。
308 | $currentSize = strlen(urlencode($json));
309 | if ($currentSize + $totalSize >= self::PUSH_MAX_SIZE * 1024 * 1024) {
310 | $txt = $this->add($buffer, $tableName);
311 | $return = json_decode($txt, true);
312 | if ('OK' != $return['status']) {
313 | throw new Exception("Api returns error: " . $txt .
314 | ". Latest successful posted primary key is {$latestPrimaryKey}.");
315 | } else {
316 | // 计算每秒钟的push的频率并如果超过频率则sleep。
317 | $newTime = microtime(true);
318 | $timeFreq++;
319 |
320 | if (floor($newTime) == $time && $timeFreq >= self::PUSH_FREQUENCE) {
321 | usleep((floor($newTime) + 1 - $newTime) * 1000000);
322 | $timeFreq = 0;
323 | }
324 |
325 | $newTime = floor(microtime(true));
326 | if ($time != $newTime) {
327 | $time = $newTime;
328 | $timeFreq = 0;
329 | }
330 |
331 | if (is_array($buffer) && !empty($buffer)) {
332 | $last = count($buffer) - 1;
333 | $latestPrimaryKey = $buffer[$last][$primaryKeyPos];
334 | } else {
335 | $latestPrimaryKey = 0;
336 | }
337 | }
338 | $buffer = array();
339 | $totalSize = 0;
340 | }
341 | $buffer[] = $item;
342 | $totalSize += $currentSize;
343 | }
344 |
345 | $lineNo++;
346 | }
347 |
348 | if (!empty($buffer)) {
349 | $return = json_decode($this->add($buffer, $tableName), true);
350 | if (self::PUSH_RETURN_STATUS_OK != $return['status']) {
351 | throw new Exception($return['errors'][0]['message'] .
352 | ". Latest successful posted line number is {$latestLine}.");
353 | }
354 | }
355 |
356 | return 'The data is posted successfully.';
357 | }
358 |
359 | /**
360 | * 推送HA3格式文档
361 | *
362 | * 除了上面的方法还可以通过文件将文档导入到指定的表中
363 | * 这里的文档需满足一定的格式,我们称之为HA3文档格式。HA3文件的要求如下:
364 | *
365 | * 文件编码:UTF-8
366 | *
367 | * 支持CMD: add, delete,update。
368 | * 如果给出的字段不是全部,add会在未给出的字段加默认值,覆盖原值;update只会更新给出的字段,未给出的不变。
369 | *
370 | * 文件分隔符:
371 | *
372 | * 373 | * 编码-------描述--------------------显示形态 374 | * "\x1E\n" 每个doc的分隔符. ^^(接换行符) 375 | * "\x1F\n" 每个字段key和value分隔 ^_(接换行符) 376 | * "\x1D" 多值字段的分隔符 ^] 377 | *; 378 | * 379 | * 示例: 380 | * 381 | *
; 382 | * CMD=add^_ 383 | * url=http://www.opensearch.console.aliyun.com^_ 384 | * title=开放搜索^_ 385 | * body=xxxxx_xxxx^_ 386 | * multi_value_feild=123^]1234^]12345^_ 387 | * ^^ 388 | * CMD=update^_ 389 | * ... 390 | *391 | * 392 | * 注意:文件结尾的分隔符也必需为"^^\n",最后一个换行符不能省略。 393 | * 394 | * @param string $fileName 指定HA3DOC所有在的路径。 395 | * @param string $tableName 指定要导入的表的名称。 396 | * @param int $offset 指定偏移行数,如果非0,则从当前行一下的数据开始导入。默认值为:1 397 | * @param number $maxSize 指定每次导入到api接口的数据量的大小,单位MB,默认值为:4 398 | * @param int $frequence 指定每秒钟导入的频率,单位次/秒,默认值为:4 399 | * @throws Exception 如果在导入的过程中由于字段问题或接口问题则抛出异常。 400 | * @return string 返回导入成功标志。 401 | */ 402 | public function pushHADocFile($fileName, $tableName, $offset = 1, 403 | $maxSize = self::PUSH_MAX_SIZE, $frequence = self::PUSH_FREQUENCE) { 404 | $reader = $this->_connect($fileName); 405 | 406 | // 默认doc初始结构。 407 | $doc = array('cmd' => '', 'fields' => array()); 408 | 409 | // 当前行号,用来记录当前已经解析到了第多少行。 410 | $lineNumber = 1; 411 | 412 | // 最新成功push数据的行号,用于如果在重新上传的时候设定offset偏移行号。 413 | $lastLineNumber = 0; 414 | 415 | // 最后更新的doc中的字段名,如果此行没有字段结束符,则下行的数据会被添加到这行的字段上。 416 | // 有一些富文本,在当前行没有结束此字段,则要记录最后的字段名称。 417 | // 例如: 418 | // rich_text=鲜花 419 | // 礼品专卖店^_ 420 | // other_field=xxx^_ 421 | $lastField = ''; 422 | 423 | // 当前还未上传的文档的大小。单位MB. 424 | $totalSize = 0; 425 | 426 | // 当前秒次已经发了多少次请求,用于限流。 427 | $timeFreq = 0; 428 | 429 | $time = time(); 430 | 431 | $buffer = array(); 432 | 433 | // 开始遍历文件。 434 | try { 435 | while ($line = fgets($reader)) { 436 | 437 | // 如果当前的行号小于设定的offset行号时跳过。 438 | if ($lineNumber < $offset) { 439 | continue; 440 | } 441 | 442 | // 获取结果当前行的最后两个字符。 443 | $separator = substr($line, -2); 444 | 445 | // 如果当前结束符是文档的结束符^^\n,则当前doc解析结束。并计算buffer+当前doc文档的 446 | // 大小,如果大于指定的文档大小,则push buffer到api,并清空buffer,同时把当前doc 447 | // 文档扔到buffer中。 448 | if ($separator == self::HA_DOC_ITEM_SEPARATOR) { 449 | 450 | $lastField = ''; 451 | 452 | // 获取当前文档生成json并urlencode之后的size大小。 453 | $json = json_encode($doc); 454 | $currentSize = strlen(urlencode($json)); 455 | 456 | // 如果计算的大小+buffer的大小大于等于限定的阀值self::PUSH_MAX_SIZE,则push 457 | // buffer数据。 458 | if ($currentSize + $totalSize >= self::PUSH_MAX_SIZE * 1024 * 1024) { 459 | 460 | // push 数据到api。 461 | $return = json_decode($this->add($buffer, $tableName), true); 462 | // 如果push不成功则抛出异常。 463 | if ('OK' != $return['status']) { 464 | throw new Exception("Api returns error. " . $return['errors'][0]['message']); 465 | } else { 466 | // 如果push成功,则计算每秒钟的push的频率并如果超过频率则sleep。 467 | $lastLineNumber = $lineNumber; 468 | $newTime = microtime(true); 469 | $timeFreq++; 470 | 471 | // 如果时间为上次的push时间且push频率超过设定的频率,则unsleep 剩余的毫秒数。 472 | if (floor($newTime) == $time && $timeFreq >= self::PUSH_FREQUENCE) { 473 | usleep((floor($newTime) + 1 - $newTime) * 1000000); 474 | $timeFreq = 0; 475 | } 476 | // 重新设定时间和频率。 477 | $newTime = floor(microtime(true)); 478 | if ($time != $newTime) { 479 | $time = $newTime; 480 | $timeFreq = 0; 481 | } 482 | } 483 | 484 | // 重置buffer为空,并重新设定total size 为0; 485 | $buffer = array(); 486 | $totalSize = 0; 487 | } 488 | // doc 添加到buffer中,并增加total size的大小。 489 | $buffer[] = $doc; 490 | $totalSize += $currentSize; 491 | 492 | // 初始化doc。 493 | $doc = array('cmd' => '', 'fields' => array()); 494 | } else if ($separator == self::HA_DOC_FIELD_SEPARATOR) { 495 | // 表示当前字段结束。 496 | $detail = substr($line, 0, -2); 497 | 498 | if (!empty($lastField)) { 499 | 500 | // 表示当前行非第一行数据,则获取最后生成的字段名称并给其赋值。 501 | $doc['fields'][$lastField] = 502 | $this->_extractFieldValue($doc['fields'][$lastField] . $detail); 503 | } else { 504 | 505 | // 表示当前为第一行数据,则解析key 和value。 506 | list($key, $value) = $this->_parseHADocField($detail); 507 | 508 | if (strtoupper($key) == 'CMD') { 509 | $doc['cmd'] = strtoupper($value); 510 | } else { 511 | $doc['fields'][$key] = $this->_extractFieldValue($value); 512 | } 513 | } 514 | 515 | // 设置字段名称为空。 516 | $lastField = ''; 517 | } else { 518 | // 此else 表示富文本的非最后一行。 519 | 520 | // 表示富文本非第一行。 521 | if (!empty($lastField)) { 522 | $doc['fields'][$lastField] .= $line; 523 | } else { 524 | // 表示字段的第一行数据。 525 | list($key, $value) = $this->_parseHADocField($line); 526 | 527 | $doc['fields'][$key] = $value; 528 | $lastField = $key; 529 | } 530 | } 531 | $lineNumber++; 532 | } 533 | 534 | fclose($reader); 535 | 536 | // 如果buffer 中还有数据则再push一次数据。 537 | if (!empty($buffer)) { 538 | $return = json_decode($this->add($buffer, $tableName), true); 539 | if (self::PUSH_RETURN_STATUS_OK != $return['status']) { 540 | throw new Exception("Api returns error. " . $return['errors'][0]['message']); 541 | } 542 | } 543 | 544 | if (!empty($doc['fields'])) { 545 | throw new Exception('Fail to push doc:' . json_encode($doc)); 546 | } 547 | 548 | return json_encode( 549 | array('status' => 'OK', 'message' => 'The data is posted successfully.') 550 | ); 551 | } catch (Exception $e) { 552 | throw new Exception( 553 | $e->getMessage() . 554 | '. Latest posted successful line no is ' . $lastLineNumber 555 | ); 556 | } 557 | } 558 | 559 | /** 560 | * 创建一个文件指针资源。 561 | * @param string $fileName 562 | * @throws Exception 563 | * @return resource 返回文件指针。 564 | */ 565 | private function _connect($fileName) 566 | { 567 | $reader = fopen($fileName, "r"); 568 | if (!$reader) { 569 | throw new Exception("The file is not exists or not readabled. Please 570 | check your file."); 571 | } 572 | return $reader; 573 | } 574 | 575 | /** 576 | * 解析一段字符串并生成key和value。 577 | * @param string $string 578 | * @return string|boolean 返回一个数组有两个字段,第一个为key,第二个为value。如果解析 579 | * 失败则返回错误。 580 | */ 581 | private function _parseHADocField($string) 582 | { 583 | $separater = '='; 584 | $pos = strpos($string, $separater); 585 | 586 | if ($pos !== false) { 587 | $key = substr($string, 0, $pos); 588 | $value = substr($string, $pos + 1); 589 | return array($key, $value); 590 | } else { 591 | throw new Exception('The are no key and value in the field.'); 592 | } 593 | } 594 | 595 | /** 596 | * 检查字段值的值是否为多值字段,如果是则返回多值的数组,否则返回一个string的结果。 597 | * @param string $value 需要解析的结果。 598 | * @return string|string 如果非多值则返回字符串,否则返回多值数组。 599 | */ 600 | private function _extractFieldValue($value) 601 | { 602 | $split = explode(self::HA_DOC_MULTI_VALUE_SEPARATOR, $value); 603 | return count($split) > 1 ? $split : $split[0]; 604 | } 605 | } 606 | -------------------------------------------------------------------------------- /src/Sdk/CloudsearchClient.php: -------------------------------------------------------------------------------- 1 | key_type = $key_type; 197 | 198 | if ($this->key_type == 'opensearch') { 199 | $this->clientId = $key; 200 | $this->clientSecret = $secret; 201 | } elseif ($this->key_type == 'aliyun') { 202 | $this->accessKeyId = $key; 203 | $this->secret = $secret; 204 | } else { 205 | $this->key_type = 'opensearch'; 206 | $this->clientId = $key; 207 | $this->clientSecret = $secret; 208 | } 209 | if (isset($opts['host']) && !empty($opts['host'])) { 210 | //对于用户通过参数指定的host,需要检查host结尾是否有/,有则去掉 211 | if (substr($opts['host'], -1) == "/") { 212 | $this->host = trim($opts['host'], '/'); 213 | } else { 214 | $this->host = $opts['host']; 215 | } 216 | } 217 | 218 | if (isset($opts['version']) && !empty($opts['version'])) { 219 | $this->version = $opts['version']; 220 | } 221 | 222 | if (isset($opts['timeout']) && !empty($opts['timeout'])) { 223 | $this->timeout = $opts['timeout']; 224 | } 225 | 226 | if (isset($opts['connect_timeout']) && !empty($opts['connect_timeout'])) { 227 | $this->connect_timeout = $opts['connect_timeout']; 228 | } 229 | 230 | if (isset($opts['gzip']) && $opts['gzip'] == true) { 231 | $this->gzip = true; 232 | } 233 | 234 | if (isset($opts['debug']) && $opts['debug'] == true) { 235 | $this->debug = true; 236 | } 237 | 238 | if (isset($opts['signatureMethod']) && !empty($opts['signatureMethod'])) { 239 | $this->signatureMethod = $opts['signatureMethod']; 240 | } 241 | 242 | if (isset($opts['signatureVersion']) && !empty($opts['signatureVersion'])) { 243 | $this->signatureVersion = $opts['signatureVersion']; 244 | } 245 | 246 | $this->baseURI = rtrim($this->host, '/'); 247 | 248 | } 249 | 250 | /** 251 | * 请求服务器 252 | * 253 | * 向服务器发出请求并获得返回结果。 254 | * 255 | * @param string $path 当前请求的path路径。 256 | * @param array $params 当前请求的所有参数数组。 257 | * @param string $method 当前请求的方法。默认值为:GET 258 | * @return string 返回获取的结果。 259 | * @donotgeneratedoc 260 | */ 261 | public function call($path, $params = array(), $method = self::METHOD) 262 | { 263 | $url = $this->baseURI . $path; 264 | if ($this->key_type == 'opensearch') { 265 | $params['client_id'] = $this->clientId; 266 | $params['nonce'] = $this->_nonce(); 267 | $params['sign'] = $this->_sign($params); 268 | } else { 269 | $params['Version'] = $this->version; 270 | $params['AccessKeyId'] = $this->accessKeyId; 271 | $params['SignatureMethod'] = $this->signatureMethod; 272 | $params['SignatureVersion'] = $this->signatureVersion; 273 | $params['SignatureNonce'] = $this->_nonce_aliyun(); 274 | $params['Timestamp'] = gmdate('Y-m-d\TH:i:s\Z'); 275 | $params['Signature'] = $this->_sign_aliyun($params, $method); 276 | } 277 | if ($this->connect == 'curl') { 278 | $result = json_decode($this->_curl($url, $params, $method), true); 279 | } else { 280 | $result = json_decode($this->_socket($url, $params, $method), true); 281 | } 282 | 283 | if ($result['status'] != 'OK') { 284 | $e = new OpensearchRunException($result['errors'][0]['message'], $result['errors'][0]['code']); 285 | $e->setErrors($result['errors']); 286 | 287 | throw $e; 288 | } 289 | 290 | return $result; 291 | } 292 | 293 | /** 294 | * 生成当前的nonce值。 295 | * 296 | * NOTE: $time为10位的unix时间戳。 297 | * 298 | * @return string 返回生成的nonce串。 299 | */ 300 | protected function _nonce() 301 | { 302 | $time = time(); 303 | return md5($this->clientId . $this->clientSecret . $time) . '.' . $time; 304 | } 305 | 306 | /** 307 | * 生产当前的aliyun签名方式对应的nonce值 308 | * 309 | * NOTE:这个值要保证访问唯一性,建议用如下算法,商家也可以自己设置一个唯一值 310 | * 311 | * @return string 返回生产的nonce串 312 | */ 313 | protected function _nonce_aliyun() 314 | { 315 | $microtime = $this->get_microtime(); 316 | return $microtime . mt_rand(1000, 9999); 317 | } 318 | 319 | /** 320 | * 根据参数生成当前的签名。 321 | * 322 | * 如果指定了sign_mode且sign_mode为1,则参数中的items将不会被计算签名。 323 | * 324 | * @param array $params 返回生成的签名。 325 | * @return string 326 | */ 327 | protected function _sign($params = array()) 328 | { 329 | $query = ""; 330 | if (isset($params['sign_mode']) && $params['sign_mode'] == 1) { 331 | unset($params['items']); 332 | } 333 | if (is_array($params) && !empty($params)) { 334 | ksort($params); 335 | $query = $this->_buildQuery($params); 336 | } 337 | return md5($query . $this->clientSecret); 338 | } 339 | 340 | /** 341 | * 根据参数生成当前得签名 342 | * 343 | * 如果指定了sign_mode且sign_mode为1,则参数中的items将不会被计算签名 344 | * 345 | * @param array $params 返回生成签名 346 | * @return string 347 | */ 348 | protected function _sign_aliyun($params = array(), $method = self::METHOD) 349 | { 350 | if (isset($params['sign_mode']) && $params['sign_mode'] == 1) { 351 | unset($params['items']); 352 | } 353 | $params = $this->_params_filter($params); 354 | $query = ''; 355 | $arg = ''; 356 | if (is_array($params) && !empty($params)) { 357 | while (list($key, $val) = each($params)) { 358 | $arg .= $this->_percentEncode($key) . "=" . $this->_percentEncode($val) . "&"; 359 | } 360 | $query = substr($arg, 0, count($arg) - 2); 361 | } 362 | $base_string = strtoupper($method) . '&%2F&' . $this->_percentEncode($query); 363 | return base64_encode(hash_hmac('sha1', $base_string, $this->secret . "&", true)); 364 | } 365 | 366 | /** 367 | * 过滤阿里云签名中不用来签名的参数,并且排序 368 | * 369 | * @param array $params 370 | * @return array 371 | * 372 | */ 373 | protected function _params_filter($parameters = array()) 374 | { 375 | $params = array(); 376 | while (list($key, $val) = each($parameters)) { 377 | if ($key == "Signature" || $val === "" || $val === null) { 378 | continue; 379 | } else { 380 | $params[$key] = $parameters[$key]; 381 | } 382 | } 383 | ksort($params); 384 | reset($params); 385 | return $params; 386 | } 387 | 388 | protected function _percentEncode($str) 389 | { 390 | // 使用urlencode编码后,将"+","*","%7E"做替换即满足 API规定的编码规范 391 | $res = urlencode($str); 392 | $res = preg_replace('/\+/', '%20', $res); 393 | $res = preg_replace('/\*/', '%2A', $res); 394 | $res = preg_replace('/%7E/', '~', $res); 395 | return $res; 396 | } 397 | /** 398 | * 通过curl的方式获取请求结果。 399 | * @param string $url 请求的URI。 400 | * @param array $params 请求的参数数组。 401 | * @param string $method 请求的方法,默认为self::METHOD。 402 | * @return string 返回获取的结果。 403 | */ 404 | private function _curl($url, $params = array(), $method = self::METHOD) 405 | { 406 | $query = $this->_buildQuery($params); 407 | $method = strtoupper($method); 408 | 409 | if ($method == self::METHOD_GET) { 410 | $url .= preg_match('/\?/i', $url) ? '&' . $query : '?' . $query; 411 | } else { 412 | $method = self::METHOD_POST; 413 | } 414 | 415 | $options = array( 416 | CURLOPT_HTTP_VERSION => 'CURL_HTTP_VERSION_1_1', 417 | CURLOPT_CONNECTTIMEOUT => $this->connect_timeout, 418 | CURLOPT_TIMEOUT => $this->timeout, 419 | CURLOPT_CUSTOMREQUEST => $method, 420 | CURLOPT_HEADER => false, 421 | CURLOPT_RETURNTRANSFER => true, 422 | CURLOPT_USERAGENT => "opensearch/php sdk " . $this->sdkVersion, //php sdk 版本信息 423 | CURLOPT_HTTPHEADER => array('Expect:'), 424 | ); 425 | 426 | if ($method == self::METHOD_POST) { 427 | $options[CURLOPT_POSTFIELDS] = $params; 428 | } 429 | 430 | if ($this->gzip) { 431 | $options[CURLOPT_ENCODING] = 'gzip'; 432 | } 433 | 434 | $session = curl_init($url); 435 | curl_setopt_array($session, $options); 436 | $response = curl_exec($session); 437 | $info = curl_getinfo($session); 438 | 439 | if ($this->debug) { 440 | $this->debugInfo = $info; //query基本信息,供调试使用 441 | } 442 | 443 | curl_close($session); 444 | 445 | return $response; 446 | } 447 | 448 | /** 449 | * 通过socket的方式获取请求结果。 450 | * @param string $url 请求的URI。 451 | * @param array $params 请求的参数数组。 452 | * @param string $method 请求方法,默认为self::METHOD。 453 | * @throws Exception 454 | * @return string 455 | */ 456 | private function _socket($url, $params = array(), $method = self::METHOD) 457 | { 458 | $method = strtoupper($method); 459 | 460 | $parse = $this->_parseUrl($url); 461 | $content = $this->_buildRequestContent( 462 | $parse, 463 | $method, 464 | http_build_query($params, '', '&') 465 | ); 466 | if ($this->debug) { 467 | $this->debugInfo = $content; 468 | } 469 | 470 | $socket = fsockopen( 471 | $parse['host'], 472 | $parse["port"], 473 | $errno, 474 | $errstr, 475 | $this->connect_timeout 476 | ); 477 | 478 | stream_set_timeout($socket, $this->timeout); 479 | 480 | if (!$socket) { 481 | throw new OpensearchCallException("Connect " . $parse['host'] . ' fail.'); 482 | } 483 | 484 | $response = ''; 485 | fwrite($socket, $content); 486 | while ($data = fgets($socket)) { 487 | $response .= $data; 488 | } 489 | fclose($socket); 490 | 491 | $ret = $this->_parseResponse($response); 492 | return $ret['result']; 493 | } 494 | 495 | /** 496 | * 调试接口 497 | * 498 | * 获取SDK调用的调试信息,需要指定debug=true才能使用 499 | * 500 | * @return array\null 调试开关(debug)打开时返回调试信息。 501 | */ 502 | public function getRequest() 503 | { 504 | if ($this->debug) { 505 | return $this->debugInfo; 506 | } else { 507 | return null; 508 | } 509 | } 510 | 511 | /** 512 | * 解析http返回的结果,并分析出response 头和body。 513 | * @param string $response_text 514 | * @return array 515 | */ 516 | private function _parseResponse($response) 517 | { 518 | list($headerContent) = explode("\r\n\r\n", $response); 519 | $header = $this->_parseHttpSocketHeader($headerContent); 520 | $response = trim(stristr($response, "\r\n\r\n"), "\r\n"); 521 | 522 | $ret = array(); 523 | $ret["result"] = 524 | (isset($header['Content-Encoding']) && 525 | trim($header['Content-Encoding']) == 'gzip') ? 526 | $this->_gzdecode($response, $header) : $this->_checkChunk($response, $header); 527 | $ret["info"]["http_code"] = 528 | isset($header["http_code"]) ? $header["http_code"] : 0; 529 | $ret["info"]["headers"] = $header; 530 | 531 | return $ret; 532 | } 533 | 534 | /** 535 | * 生成http头信息。 536 | * 537 | * @param array $parse 538 | * @param string $method HTTP方法。 539 | * @param string $data HTTP参数串。 540 | * @return string 541 | */ 542 | private function _buildRequestContent(&$parse, $method, $data) 543 | { 544 | $strLength = ''; 545 | $content = ''; 546 | 547 | if ($method == self::METHOD_GET) { 548 | $data = ltrim($data, '&'); 549 | $query = isset($parse['query']) ? $parse['query'] : ''; 550 | $parse['path'] .= ($query ? '&' : '?') . $data; 551 | } else { 552 | $method = self::METHOD_POST; 553 | $strLength = "Content-length: " . strlen($data) . "\r\n"; 554 | $content = $data; 555 | } 556 | 557 | $write = $method . " " . $parse['path'] . " HTTP/1.0\r\n"; 558 | $write .= "Host: " . $parse['host'] . "\r\n"; 559 | $write .= "Content-type: application/x-www-form-urlencoded\r\n"; 560 | $write .= "User-Agent: opensearch/php sdk " . $this->sdkVersion . "\r\n"; 561 | if ($this->gzip) { 562 | $write .= "Accept-Encoding: gzip\r\n"; 563 | } 564 | $write .= $strLength; 565 | $write .= "Connection: close\r\n\r\n"; 566 | $write .= $content; 567 | 568 | return $write; 569 | } 570 | 571 | /** 572 | * 把数组生成http请求需要的参数。 573 | * @param array $params 574 | * @return string 575 | */ 576 | private function _buildQuery($params) 577 | { 578 | $args = http_build_query($params, '', '&'); 579 | // remove the php special encoding of parameters 580 | // see http://www.php.net/manual/en/function.http-build-query.php#78603 581 | //return preg_replace('/%5B(?:[0-9]|[1-9][0-9]+)%5D=/', '=', $args); 582 | return $args; 583 | } 584 | 585 | /** 586 | * 解析URL并生成host、schema、path、query等信息。 587 | * @param string $url 588 | * @throws Exception 589 | * @return Ambigous
39 | * $search = new CloudsearchSearch($client);
40 | * $search->addIndex('my_indexname');
41 | * $search->search();
42 | *
43 | *
44 | */
45 | class CloudsearchSearch
46 | {
47 | /**
48 | * 设定搜索结果集升降排序的标志,"+"为升序,"-"为降序。
49 | *
50 | * @var string
51 | */
52 | const SORT_INCREASE = '+';
53 | const SORT_DECREASE = '-';
54 |
55 | const SEARCH_TYPE_SCAN = "scan";
56 |
57 | /**
58 | * 和API服务进行交互的对象。
59 | * @var CloudsearchClient
60 | */
61 | private $client;
62 |
63 | /**
64 | * 此次检索指定的应用名称。
65 | *
66 | * 可以指定单个应用名称,也可以指定多个应用名称结合。
67 | *
68 | * @var array
69 | */
70 | private $indexes = array();
71 |
72 | /**
73 | * 指定某些字段的一些summary展示规则。
74 | *
75 | * 这些字段必需为可分词的text类型的字段。
76 | *
77 | * 例如:
78 | * 指定title字段为: summary_field=>title
79 | * 指定title长度为50:summary_len=>50
80 | * 指定title飘红标签:summary_element=>em
81 | * 指定title省略符号:summary_ellipsis=>...
82 | * 指定summary缩略段落个数:summary_snipped=>1
83 | * 那么当前的字段值为:
84 | *
85 | * array('title' => array(
86 | * 'summary_field' => 'title',
87 | * 'summary_len' => 50,
88 | * 'summary_element' => 'em',
89 | * 'summary_ellipsis' => '...',
90 | * 'summary_snipped' => 1,
91 | * 'summary_element_prefix' => 'em',
92 | * 'summary_element_postfix' => '/em')
93 | * );
94 | *
95 | * @var array
96 | */
97 | private $summary = array();
98 |
99 | /**
100 | * config 子句。
101 | *
102 | * config子句只能接收三个参数(start, format, hit),其中:
103 | * start为当前结果集的偏移量;
104 | * format为当前返回结果的格式,有json,xml和protobuf三种格式;
105 | * hit为当前获取结果条数。
106 | *
107 | * 例如 "start:0,format:xml,hit:20"
108 | *
109 | * @var string
110 | */
111 | private $clauseConfig = '';
112 |
113 | /**
114 | * 返回的数据的格式,有json、xml,protobuf三种类型可选;默认为XML格式。
115 | * @var string
116 | */
117 | private $format = 'xml';
118 |
119 | /**
120 | * 设定返回结果集的offset,默认为0。
121 | * @var int
122 | */
123 | private $start = 0;
124 |
125 | /**
126 | * 设定返回结果集的个数,默认为20。
127 | * @var int
128 | */
129 | private $hits = 20;
130 |
131 | /**
132 | * 设定排序规则。
133 | * @var array
134 | */
135 | private $sort = array();
136 |
137 | /**
138 | * 设定过滤条件。
139 | * @var string
140 | */
141 | private $filter = '';
142 |
143 | /**
144 | * aggregate设定规则。
145 | * @var array
146 | */
147 | private $aggregate = array();
148 |
149 | /**
150 | * distinct 排序。
151 | * @var array
152 | */
153 | private $distinct = array();
154 |
155 | /**
156 | * 返回字段过滤。
157 | *
158 | * 如果设定了此字段,则只返回此字段里边的field。
159 | * @var array
160 | */
161 | private $fetches = array();
162 |
163 | /**
164 | * rerankSize表示参与精排算分的文档个数,一般不用使用默认值就能满足,不用设置,会自动使用默认值200
165 | * @var int
166 | */
167 | private $rerankSize = 200;
168 |
169 | /**
170 | * query 子句。
171 | *
172 | * query子句可以为query='鲜花',也可以指定索引来搜索,例如:query=title:'鲜花'。
173 | * 详情请浏览setQueryString($query)方法。
174 | *
175 | * @var string
176 | */
177 | private $query;
178 |
179 | /**
180 | * 指定表达式名称,表达式名称和结构在网站中指定。
181 | *
182 | *
183 | * @var string
184 | */
185 | private $formulaName = '';
186 |
187 | /**
188 | * 指定粗排表达式名称,表达式名称和结构在网站中指定。
189 | * @var string
190 | */
191 | private $firstFormulaName = '';
192 |
193 | /**
194 | * 指定kvpairs子句的内容,内容为k1:v1,k2:v2的方式表示。
195 | * @var string
196 | */
197 | private $kvpair = '';
198 |
199 | /**
200 | * 指定qp 名称。
201 | * @var array
202 | */
203 | private $QPName = array();
204 |
205 | /**
206 | * 指定关闭的方法名称。
207 | * @var unknown
208 | */
209 | private $functions = array();
210 |
211 | /**
212 | * 设定自定义参数。
213 | *
214 | * 如果api有新功能(参数)发布,用户不想更新sdk版本,则可以自己来添加自定义的参数。
215 | *
216 | * @var string
217 | */
218 | private $customParams = array();
219 |
220 | private $scrollId = null;
221 |
222 | private $searchType = '';
223 |
224 | private $scroll = null;
225 |
226 | /**
227 | * 请求API的部分path。
228 | * @var string
229 | */
230 | private $path = '/search';
231 |
232 | /**
233 | * 构造函数
234 | *
235 | * @param CloudsearchClient $client 此对象由CloudsearchClient类实例化。
236 | */
237 | public function __construct($client)
238 | {
239 | $this->client = $client;
240 | }
241 |
242 | /**
243 | * 设置scroll扫描起始id
244 | *
245 | * @param scrollId 扫描起始id
246 | */
247 | public function setScrollId($scrollId)
248 | {
249 | $this->scrollId = $scrollId;
250 | }
251 |
252 | /**
253 | * 获取scroll扫描起始id
254 | *
255 | * @return string 扫描起始id
256 | */
257 | public function getScrollId()
258 | {
259 | return $this->scrollId;
260 | }
261 |
262 | /**
263 | * 请求scroll api。
264 | *
265 | * 类似search接口,但是不支持sort, aggregate, distinct, formula_name, summary及qp,
266 | * start 等功能。
267 | *
268 | * scroll实现方式:
269 | * 第一次正常带有指定的子句和参数调用scroll接口,此接口会返回scroll_id信息。
270 | * 第二次请求时只带此scroll_id信息和scroll参数即可。
271 | *
272 | * 类似第一次请求:
273 | * $search = new CloudsearchSearch($client);
274 | * $search->addIndex("juhuasuan");
275 | * $search->setQueryString("default:'酒店'");
276 | * $search->setFormat('json');
277 | * $search->setHits(10);
278 | * $search->setScroll("1m");
279 | * $result = $search->scroll();
280 | *
281 | * $array = json_decode($result, true);
282 | * $scrollId = $array['result']['scroll_id'];
283 | *
284 | * 第二次请求:
285 | * $search = new CloudsearchSearch($client);
286 | * $search->setScroll("1m");
287 | * $search->setScrollId($scrollId);
288 | * $result = $search->scroll();
289 | *
290 | * @param array $opts 扫描请求所需参数
291 | * @return string 扫描结果
292 | */
293 | public function scroll($opts = array())
294 | {
295 | $this->extract($opts, "scroll");
296 | return $this->call('scroll');
297 | }
298 |
299 | /**
300 | * 执行搜索
301 | *
302 | * 执行向API提出搜索请求。
303 | * 更多说明请参见 [API 配置config子句]({{!api-reference/query-clause&config-clause!}})
304 | * @param array $opts 此参数如果被复制,则会把此参数的内容分别赋给相应的变量。此参数的值可能有以下内容:
305 | * @subparam string query 指定的搜索查询串,可以为query=>"索引名:'鲜花'"。
306 | * @subparam array indexes 指定的搜索应用,可以为一个应用,也可以多个应用查询。
307 | * @subparam array fetch_fields 设定返回的字段列表,如果只返回url和title,则为 array('url', 'title')。
308 | * @subparam string format 指定返回的数据格式,有json,xml和protobuf三种格式可选。默认值为:'xml'
309 | * @subparam string formula_name 指定的表达式名称,此名称需在网站中设定。
310 | * @subparam array summary 指定summary字段一些标红、省略、截断等规则。
311 | * @subparam int start 指定搜索结果集的偏移量。默认为0。
312 | * @subparam int hits 指定返回结果集的数量。默认为20。
313 | * @subparam array sort 指定排序规则。默认值为:'self::SORT_DECREASE' (降序)
314 | * @subparam string filter 指定通过某些条件过滤结果集。
315 | * @subparam array aggregate 指定统计类的信息。
316 | * @subparam array distinct 指定distinct排序。
317 | * @subparam string kvpair 指定的kvpair。
318 | *
319 | * @return string 返回搜索结果。
320 | *
321 | */
322 | public function search($opts = array())
323 | {
324 | $this->extract($opts);
325 | return $this->call();
326 | }
327 |
328 | /**
329 | * 增加新的应用来进行检索
330 | * @param string\array $indexName 应用名称或应用名称列表.
331 | */
332 | public function addIndex($indexName)
333 | {
334 | if (is_array($indexName)) {
335 | $this->indexes = $indexName;
336 | } else {
337 | $this->indexes[] = $indexName;
338 | }
339 | $this->indexes = array_unique($this->indexes);
340 | }
341 |
342 | /**
343 | * 删除待搜索的应用
344 | *
345 | * 在当前检索中删除此应用的检索结果。
346 | * @param string $indexName 待删除的应用名称
347 | */
348 | public function removeIndex($indexName)
349 | {
350 | $flip = array_flip($this->indexes);
351 | unset($flip[$indexName]);
352 | $this->indexes = array_values(array_flip($flip));
353 | }
354 |
355 | /**
356 | * 获得请求应用列表
357 | *
358 | * 当前请求中所有的应用名列表。
359 | *
360 | * @return array 返回当前搜索的所有应用列表。
361 | */
362 | public function getSearchIndexes()
363 | {
364 | return $this->indexes;
365 | }
366 |
367 | /**
368 | * 设置表达式名称
369 | * 此表达式名称和结构需要在网站中已经设定。
370 | * @param string $formulaName 表达式名称。
371 | */
372 | public function setFormulaName($formulaName)
373 | {
374 | $this->formulaName = $formulaName;
375 | }
376 |
377 | /**
378 | * 获取表达式名称
379 | *
380 | * 获得当前请求中设置的表达式名称。
381 | *
382 | * @return string 返回当前设定的表达式名称。
383 | */
384 | public function getFormulaName()
385 | {
386 | return $this->formulaName;
387 | }
388 |
389 | /**
390 | * 清空精排表达式名称设置
391 | */
392 | public function clearFormulaName()
393 | {
394 | $this->formulaName = '';
395 | }
396 |
397 | /**
398 | * 设置粗排表达式名称
399 | *
400 | * 此表达式名称和结构需要在网站中已经设定。
401 | *
402 | * @param string $FormulaName 表达式名称。
403 | */
404 | public function setFirstFormulaName($formulaName)
405 | {
406 | $this->firstFormulaName = $formulaName;
407 | }
408 |
409 | /**
410 | * 获取粗排表达式设置
411 | *
412 | * 获取当前设置的粗排表达式名称。
413 | *
414 | * @return string 返回当前设定的表达式名称。
415 | */
416 | public function getFirstFormulaName()
417 | {
418 | return $this->firstFormulaName;
419 | }
420 |
421 | /**
422 | * 清空粗排表达式名称设置
423 | */
424 | public function clearFirstFormulaName()
425 | {
426 | $this->firstFormulaName = '';
427 | }
428 |
429 | /**
430 | * 添加一条summary信息
431 | * @param string $fieldName 指定的生效的字段。此字段必需为可分词的text类型的字段。
432 | * @param string $len 指定结果集返回的词字段的字节长度,一个汉字为2个字节。
433 | * @param string $element 指定命中的query的标红标签,可以为em等。
434 | * @param string $ellipsis 指定用什么符号来标注未展示完的数据,例如“...”。
435 | * @param string $snipped 指定query命中几段summary内容。
436 | * @param string $elementPrefix 如果指定了此参数,则标红的开始标签以此为准。
437 | * @param string $elementPostfix 如果指定了此参数,则标红的结束标签以此为准。
438 | */
439 | public function addSummary($fieldName, $len = 0, $element = '',
440 | $ellipsis = '', $snipped = 0, $elementPrefix = '', $elementPostfix = '') {
441 | if (empty($fieldName)) {
442 | return false;
443 | }
444 |
445 | $summary = array();
446 | $summary['summary_field'] = $fieldName;
447 | empty($len) || $summary['summary_len'] = (int) $len;
448 | empty($element) || $summary['summary_element'] = $element;
449 | empty($ellipsis) || $summary['summary_ellipsis'] = $ellipsis;
450 | empty($snipped) || $summary['summary_snipped'] = $snipped;
451 | empty($elementPrefix) || $summary['summary_element_prefix'] = $elementPrefix;
452 | empty($elementPostfix) || $summary['summary_element_postfix'] = $elementPostfix;
453 |
454 | $this->summary[$fieldName] = $summary;
455 | }
456 |
457 | /**
458 | * 获取当前的summary信息
459 | * 可以通过指定字段名称返回指定字段的summary信息
460 | *
461 | * @param string $field 指定的字段,如果此字段为空,则返回整个summary信息,否则返回指定field的summary信息。
462 | * @return array 返回summary信息。
463 | */
464 | public function getSummary($field = '')
465 | {
466 | return (!empty($field)) ? $this->summary[$field] : $this->summary;
467 | }
468 |
469 | /**
470 | * 获取summary字符串
471 | *
472 | * 把summary信息生成字符串并返回。
473 | *
474 | * @return string 返回字符串的summary信息。
475 | */
476 | public function getSummaryString()
477 | {
478 | $summary = array();
479 | if (is_array($s = $this->getSummary()) && !empty($s)) {
480 | foreach ($this->getSummary() as $summaryAttributes) {
481 | $item = array();
482 | if (is_array($summaryAttributes) && !empty($summaryAttributes)) {
483 | foreach ($summaryAttributes as $k => $v) {
484 | $item[] = $k . ":" . $v;
485 | }
486 | }
487 | $summary[] = implode(",", $item);
488 | }
489 | }
490 | return implode(";", $summary);
491 | }
492 |
493 | /**
494 | * 设置返回的数据格式
495 | *
496 | * @param string $format 数据格式名称,有xml, json和protobuf 三种类型。
497 | */
498 | public function setFormat($format)
499 | {
500 | $this->format = $format;
501 | }
502 |
503 | /**
504 | * 获取当前的数据格式名称
505 | *
506 | * @return string 返回当前的数据格式名称。
507 | */
508 | public function getFormat()
509 | {
510 | return $this->format;
511 | }
512 |
513 | /**
514 | * 设置返回结果的offset偏移量
515 | *
516 | * @param int $start 偏移量。
517 | */
518 | public function setStartHit($start)
519 | {
520 | $this->start = (int) $start;
521 | }
522 |
523 | /**
524 | * 获取返回结果的offset偏移量
525 | *
526 | * @return int 返回当前设定的偏移量。
527 | */
528 | public function getStartHit()
529 | {
530 | return $this->start;
531 | }
532 |
533 | /**
534 | * 设置结果集大小
535 | *
536 | * 设置当前返回结果集的doc个数。
537 | *
538 | * @param number $hits 指定的doc个数。默认值:20
539 | */
540 | public function setHits($hits = 20)
541 | {
542 | $this->hits = (int) $hits;
543 | }
544 |
545 | /**
546 | * 获取结果集大小
547 | *
548 | * 获取当前设定的结果集的doc数。
549 | *
550 | * @return number 返回当前指定的doc个数。
551 | */
552 | public function getHits()
553 | {
554 | return $this->hits;
555 | }
556 |
557 | /**
558 | * 添加排序设置
559 | *
560 | * 增加一个排序字段及排序方式。
561 | * 更多说明请参见[API 排序sort子句]({{!api-reference/query-clause&sort-clause!}})
562 | * @param string $field 字段名称。
563 | * @param string $sortChar 排序方式,有升序+和降序-两种方式。
564 | */
565 | public function addSort($field, $sortChar = self::SORT_DECREASE)
566 | {
567 | $this->sort[$field] = $sortChar;
568 | }
569 |
570 | /**
571 | * 删除指定字段的排序
572 | *
573 | * @param string $field 指定的字段名称。
574 | */
575 | public function removeSort($field)
576 | {
577 | unset($this->sort[$field]);
578 | }
579 |
580 | /**
581 | * 获取排序信息
582 | *
583 | * @param string $sortKey 如果此字段为空,则返回所有排序信息,否则只返回指定字段的排序值。
584 | * @return string\array 返回排序值。
585 | */
586 | public function getSort($sortKey = '')
587 | {
588 | if (!empty($sortKey)) {
589 | return $this->sort[$sortKey];
590 | } else {
591 | return $this->sort;
592 | }
593 | }
594 |
595 | /**
596 | * 获取排序字符串
597 | *
598 | * 把排序信息生成字符串并返回。
599 | *
600 | * @return string 返回字符串类型的排序规则。
601 | */
602 | public function getSortString()
603 | {
604 | $sort = $this->getSort();
605 | $sortString = array();
606 | if (is_array($sort) && !empty($sort)) {
607 | foreach ($sort as $k => $v) {
608 | $sortString[] = $v . $k;
609 | }
610 | }
611 | return implode(";", $sortString);
612 | }
613 |
614 | /**
615 | * 添加过滤规则
616 | *
617 | * 针对指定的字段添加过滤规则。
618 | * 更多说明请参见 [API 过滤filter子句]({{!api-reference/query-clause&filter-clause!}})
619 | *
620 | * @param string $filter 过滤规则,例如fieldName>=1。
621 | * @param string $operator 操作符,可以为 AND OR。默认值为:'AND'
622 | */
623 | public function addFilter($filter, $operator = 'AND')
624 | {
625 | if (empty($this->filter)) {
626 | $this->filter = $filter;
627 | } else {
628 | $this->filter .= " {$operator} {$filter}";
629 | }
630 | }
631 |
632 | /**
633 | * 获取过滤规则
634 | *
635 | * @return filter 返回字符串类型的过滤规则。
636 | */
637 | public function getFilter()
638 | {
639 | return $this->filter;
640 | }
641 |
642 | /**
643 | * 添加统计信息相关参数
644 | *
645 | * 一个关键词通常能命中数以万计的文档,用户不太可能浏览所有文档来获取信息。而用户感兴趣的可
646 | * 能是一些统计类的信息,比如,查询“手机”这个关键词,想知道每个卖家所有商品中的最高价格。
647 | * 则可以按照卖家的user_id分组,统计每个小组中最大的price值:
648 | * groupKey:user_id, aggFun: max(price)
649 | * 更多说明请参见 [APi aggregate子句说明]({{!api-reference/query-clause&aggregate-clause!}})
650 | *
651 | * @param string $groupKey 指定的group key.
652 | * @param string $aggFun 指定的function。当前支持:count、max、min、sum。
653 | * @param string $range 指定统计范围。
654 | * @param string $maxGroup 最大组个数。
655 | * @param string $aggFilter 表示仅统计满足特定条件的文档。
656 | * @param string $aggSamplerThresHold 抽样统计的阈值。表示该值之前的文档会依次统计,该值之后的文档会进行抽样统计。
657 | * @param string $aggSamplerStep 抽样统计的步长。
658 | */
659 | public function addAggregate($groupKey, $aggFun, $range = '', $maxGroup = '',
660 | $aggFilter = '', $aggSamplerThresHold = '', $aggSamplerStep = '') {
661 | if (empty($groupKey) || empty($aggFun)) {
662 | return false;
663 | }
664 |
665 | $aggregate = array();
666 | $aggregate['group_key'] = $groupKey;
667 | $aggregate['agg_fun'] = $aggFun;
668 |
669 | empty($range) || $aggregate['range'] = $range;
670 | empty($maxGroup) || $aggregate['max_group'] = $maxGroup;
671 | empty($aggFilter) || $aggregate['agg_filter'] = $aggFilter;
672 | empty($aggSamplerThresHold) ||
673 | $aggregate['agg_sampler_threshold'] = $aggSamplerThresHold;
674 | empty($aggSamplerStep) || $aggregate['agg_sampler_step'] = $aggSamplerStep;
675 |
676 | $this->aggregate[$groupKey][] = $aggregate;
677 | }
678 |
679 | /**
680 | * 删除指定的统计数据
681 | *
682 | * @param string $groupKey 指定的group key。
683 | */
684 | public function removeAggregate($groupKey)
685 | {
686 | unset($this->aggregate[$groupKey]);
687 | }
688 |
689 | /**
690 | * 获取统计相关信息
691 | *
692 | * @param string $groupKey 指定group key获取其相关信息,如果为空,则返回整个信息。
693 | * @return array 统计相关信息
694 | */
695 | public function getAggregate($key = '')
696 | {
697 | return (!empty($key)) ? $this->aggregate[$key] : $this->aggregate;
698 | }
699 |
700 | /**
701 | * 获取字符串类型的统计信息
702 | *
703 | * @return string 获取字符串类型的统计信息
704 | */
705 | public function getAggregateString()
706 | {
707 | $aggregate = array();
708 | if (is_array($agg = $this->getAggregate()) && !empty($agg)) {
709 | foreach ($agg as $aggDescs) {
710 | $item = array();
711 | if (is_array($aggDescs) && !empty($aggDescs)) {
712 | foreach ($aggDescs as $aggDesc) {
713 | foreach ($aggDesc as $itemKey => $itemValue) {
714 | $item[] = $itemKey . ":" . $itemValue;
715 | }
716 | $aggregate[] = implode(",", $item);
717 | }
718 | }
719 |
720 | }
721 | }
722 | return implode(";", $aggregate);
723 | }
724 |
725 | /**
726 | * 添加distinct排序信息
727 | *
728 | * 例如:检索关键词“手机”共获得10个结果,分别为:doc1,doc2,doc3,doc4,doc5,doc6,
729 | * doc7,doc8,doc9,doc10。其中前三个属于用户A,doc4-doc6属于用户B,剩余四个属于用户C。
730 | * 如果前端每页仅展示5个商品,则用户C将没有展示的机会。但是如果按照user_id进行抽取,每轮抽
731 | * 取1个,抽取2次,并保留抽取剩余的结果,则可以获得以下文档排列顺序:doc1、doc4、doc7、
732 | * doc2、doc5、doc8、doc3、doc6、doc9、doc10。可以看出,通过distinct排序,各个用户的
733 | * 商品都得到了展示机会,结果排序更趋于合理。
734 | * 更多说明请参见 [API distinct子句]({{!api-reference/query-clause&distinct-clause!}})
735 | *
736 | * @param string $key 为用户用于做distinct抽取的字段,该字段要求建立Attribute索引。
737 | * @param int $distCount 为一次抽取的document数量,默认值为1。
738 | * @param int $distTimes 为抽取的次数,默认值为1。
739 | * @param string $reserved 为是否保留抽取之后剩余的结果,true为保留,false则丢弃,丢弃时totalHits的个数会减去被distinct而丢弃的个数,但这个结果不一定准确,默认为true。
740 | * @param string $distFilter 为过滤条件,被过滤的doc不参与distinct,只在后面的 排序中,这些被过滤的doc将和被distinct出来的第一组doc一起参与排序。默认是全部参与distinct。
741 | * @param string $updateTotalHit 当reserved为false时,设置update_total_hit为true,则最终total_hit会减去被distinct丢弃的的数目(不一定准确),为false则不减;默认为false。
742 | * @param int $maxItemCount 设置计算distinct时最多保留的doc数目。
743 | * @param number $grade 指定档位划分阈值。
744 | */
745 | public function addDistinct($key, $distCount = 0, $distTimes = 0,
746 | $reserved = '', $distFilter = '', $updateTotalHit = '',
747 | $maxItemCount = 0, $grade = '') {
748 |
749 | if (empty($key)) {
750 | return false;
751 | }
752 |
753 | $distinct = array();
754 | $distinct['dist_key'] = $key;
755 | empty($distCount) || ($distinct['dist_count'] = (int) $distCount);
756 | empty($distTimes) || $distinct['dist_times'] = (int) $distTimes;
757 | empty($reserved) || $distinct['reserved'] = $reserved;
758 | empty($distFilter) || $distinct['dist_filter'] = $distFilter;
759 | empty($updateTotalHit) || $distinct['update_total_hit'] = $updateTotalHit;
760 | empty($maxItemCount) || $distinct['max_item_count'] = (int) $maxItemCount;
761 | empty($grade) || $distinct['grade'] = $grade;
762 |
763 | $this->distinct[$key] = $distinct;
764 | }
765 |
766 | /**
767 | * 删除某个字段的所有distinct排序信息
768 | *
769 | * @param string $distinctKey 指定的字段
770 | */
771 | public function removeDistinct($distinctKey)
772 | {
773 | unset($this->distinct[$distinctKey]);
774 | }
775 |
776 | /**
777 | * 获取某字段的distinct排序信息
778 | *
779 | * @param string $key 指定的distinct字段,如果字段为空则返回所有distinct信息。
780 | * @return array 指定字段的distinct排序信息
781 | */
782 | public function getDistinct($key = '')
783 | {
784 | return (!empty($key)) ? $this->distinct[$key] : $this->distinct;
785 | }
786 |
787 | /**
788 | * 获取字符串类型的所有的distinct信息
789 | * @return string 字符串类型的所有的distinct信息
790 | */
791 | public function getDistinctString()
792 | {
793 | $distinct = array();
794 | if (is_array($s = $this->getDistinct()) && !empty($s)) {
795 | foreach ($s as $distinctAttribute) {
796 | $item = array();
797 | if ($distinctAttribute['dist_key'] != 'none_dist') {
798 | if (is_array($distinctAttribute) && !empty($distinctAttribute)) {
799 | foreach ($distinctAttribute as $k => $v) {
800 | $item[] = $k . ":" . $v;
801 | }
802 | }
803 | $distinct[] = implode(",", $item);
804 | } else {
805 | $distinct[] = $distinctAttribute['dist_key'];
806 | }
807 | }
808 | }
809 | return implode(";", $distinct);
810 | }
811 |
812 | /**
813 | * 设定指定索引字段范围的搜索关键词
814 | *
815 | * [NOTE]:$query必须指定索引名称,格式类似为 索引名称:'搜索关键词'。
816 | *
817 | * 此query是查询必需的一部分,可以指定不同的索引名,并同时可指定多个查询及之间的关系
818 | * (AND, OR, ANDNOT, RANK)。
819 | *
820 | * 例如查询subject索引字段的query:“手机”,可以设置为
821 | * query=subject:'手机'。
822 | *
823 | * NOTE: text类型索引在建立时做了分词,而string类型的索引则没有分词
824 | * 更多说明请参见 [API query子句]({{!api-reference/query-clause&query-clause!}})
825 | *
826 | * @param string $query 设定搜索的查询词。
827 | * @param string $fieldName 设定的索引范围。
828 | *
829 | */
830 | public function setQueryString($query)
831 | {
832 | $this->query = $query;
833 | }
834 |
835 | /**
836 | * 获取当前指定的查询词内容
837 | *
838 | * @return string 当前指定的查询词内容
839 | */
840 | public function getQuery()
841 | {
842 | return $this->query;
843 | }
844 |
845 | /**
846 | * 添加指定结果集返回的字段
847 | *
848 | * @param array\string $field 结果集返回的字段。
849 | */
850 | public function addFetchFields($field)
851 | {
852 | if (!is_array($field)) {
853 | if (!in_array($field, $this->fetches)) {
854 | $this->fetches[] = $field;
855 | }
856 | } else {
857 | $this->fetches = $field;
858 | }
859 | }
860 |
861 | /**
862 | * 删除指定结果集的返回字段
863 | *
864 | * @param string $fieldName 指定字段名称。
865 | */
866 | public function removeFetchField($fieldName)
867 | {
868 | $flip = array_flip($this->fetches);
869 | unset($flip[$fieldName]);
870 | $this->fetches = array_flip($flip);
871 | }
872 |
873 | /**
874 | * 设置kvpair
875 | * 更多说明请参见 [API 自定义kvpair子句]({{!api-reference/query-clause&kvpair-clause!}})
876 | *
877 | * @param string $pair 指定的pair信息。
878 | */
879 | public function setPair($pair)
880 | {
881 | $this->kvpair = $pair;
882 | }
883 |
884 | /**
885 | * 获取当前的kvpair
886 | *
887 | * @return string 返回当前设定的kvpair。
888 | */
889 | public function getPair()
890 | {
891 | return $this->kvpair;
892 | }
893 |
894 | /**
895 | * 增加自定义参数
896 | *
897 | * @param string $paramKey 参数名称。
898 | * @param string $paramValue 参数值。
899 | */
900 | public function addCustomParam($paramKey, $paramValue)
901 | {
902 | $this->customParams[$paramKey] = $paramValue;
903 | }
904 |
905 | /**
906 | * 指定精排算分的文档个数
907 | *
908 | * 若不指定则使用默认值200
909 | *
910 | * @param int $rerankSize 精排算分文档个数
911 | */
912 | public function addRerankSize($rerankSize)
913 | {
914 | $this->rerankSize = $rerankSize;
915 | }
916 |
917 | /**
918 | * 添加一条查询分析规则
919 | *
920 | * @param QPName 查询分析规则
921 | */
922 | public function addQPName($QPName)
923 | {
924 | if (is_array($QPName)) {
925 | $this->QPName = $QPName;
926 | } else {
927 | $this->QPName[] = $QPName;
928 | }
929 | }
930 |
931 | /**
932 | * 获取设置的查询分析规则
933 | *
934 | * @return String 设置的查询分析规则
935 | */
936 | public function getQPName()
937 | {
938 | return $this->QPName;
939 | }
940 |
941 | /**
942 | * 关闭某些功能模块。
943 | *
944 | * 有如下场景需要考虑:
945 | * 1、如果要关闭整个qp的功能,则参数为空即可。
946 | * 2、要指定某个索引关闭某个功能,则可以指定disableValue="processer:index",
947 | * processer:index为指定关闭某个processer的某个索引功能,其中index为索引名称,多个索引可以用“|”分隔,可以为index1[|index2...]
948 | * 3、如果要关闭多个processor可以传递数组。
949 | * qp processor 有如下模块:
950 | * 1、spell_check: 检查用户查询串中的拼写错误,并给出纠错建议。
951 | * 2、term_weighting: 分析查询中每个词的重要程度,并将其量化成权重,权重较低的词可能不会参与召回。
952 | * 3、stop_word: 根据系统内置的停用词典过滤查询中无意义的词
953 | * 4、synonym: 根据系统提供的通用同义词库和语义模型,对查询串进行同义词扩展,以便扩大召回。
954 | * example:
955 | * "" 表示关闭整个qp。
956 | * "spell_check" 表示关闭qp的拼音纠错功能。
957 | * "stop_word:index1|index2" 表示关闭qp中索引名为index1和index2上的停用词功能。
958 | *
959 | * @param string $functionName 指定的functionName,例如“qp”等
960 | * @param string|array $disableValue 需要关闭的值
961 | */
962 | public function addDisabledQP($disableValue = "")
963 | {
964 | $this->addDisabledFunction("qp", $disableValue);
965 | }
966 |
967 | /**
968 | * 添加一项禁止的功能模块
969 | *
970 | * @param functionName 功能模块名称
971 | * @param disableValue 禁用的功能细节
972 | */
973 | public function addDisabledFunction($functionName, $disableValue = "")
974 | {
975 | if (is_array($disableValue)) {
976 | $this->functions[$functionName] = $disableValue;
977 | } else {
978 | $this->functions[$functionName][] = $disableValue;
979 | }
980 | }
981 |
982 | /**
983 | * 获取所有禁止的功能模块
984 | *
985 | * @return array 所哟禁止的功能模块
986 | */
987 | public function getDisabledFunction()
988 | {
989 | return $this->functions;
990 | }
991 |
992 | /**
993 | * 以字符串的格式返回disable的内容。
994 | *
995 | * @return string
996 | */
997 | public function getDisabledFunctionString()
998 | {
999 | $functions = $this->getDisabledFunction();
1000 | $result = array();
1001 | if (!empty($functions)) {
1002 | foreach ($functions as $functionName => $value) {
1003 | $string = "";
1004 | if (is_array($value) && !empty($value)) {
1005 | $string = implode(",", $value);
1006 | }
1007 |
1008 | if ($string === "") {
1009 | $result[] = $functionName;
1010 | } else {
1011 | $result[] = $functionName . ":" . $string;
1012 | }
1013 | }
1014 | }
1015 |
1016 | return implode(";", $result);
1017 | }
1018 |
1019 | /**
1020 | * 获取精排算分文档个数
1021 | *
1022 | * @return int 精排算分文档个数
1023 | */
1024 | public function getRerankSize()
1025 | {
1026 | return $this->rerankSize;
1027 | }
1028 |
1029 | /**
1030 | * 获取自定义参数
1031 | *
1032 | * @return string 自定义参数
1033 | */
1034 | public function getCustomParam()
1035 | {
1036 | return $this->customParams;
1037 | }
1038 |
1039 | /**
1040 | * 获取指定结果集返回的字段列表
1041 | *
1042 | * @return array 指定结果集返回的字段列表
1043 | */
1044 | public function getFetchFields()
1045 | {
1046 | return $this->fetches;
1047 | }
1048 |
1049 | /**
1050 | * 设置此次获取的scroll id的期时间。
1051 | *
1052 | * 可以为整形数字,默认为毫秒。也可以用1m表示1min;支持的时间单位包括:
1053 | * w=Week, d=Day, h=Hour, m=minute, s=second
1054 | *
1055 | * @param string|int $scroll
1056 | */
1057 | public function setScroll($scroll)
1058 | {
1059 | $this->scroll = $scroll;
1060 | }
1061 |
1062 | /**
1063 | * 获取scroll的失效时间。
1064 | *
1065 | * @return string|int
1066 | */
1067 | public function getScroll()
1068 | {
1069 | return $this->scroll;
1070 | }
1071 |
1072 | /**
1073 | * 设置搜索类型
1074 | *
1075 | * @param searchType 搜索类型
1076 | */
1077 | private function setSearchType($searchType)
1078 | {
1079 | $this->searchType = $searchType;
1080 | }
1081 |
1082 | /**
1083 | * 获取设置的搜索类型
1084 | *
1085 | * @return String 设置的搜索类型
1086 | */
1087 | private function getSearchType()
1088 | {
1089 | return $this->searchType;
1090 | }
1091 |
1092 | /**
1093 | * 从$opts数组中抽取所有的需要的参数并复制到属性中。
1094 | *
1095 | * @param array $opts
1096 | */
1097 | private function extract($opts, $type = 'search')
1098 | {
1099 | if (!empty($opts) && is_array($opts)) {
1100 | isset($opts['query']) && $this->setQueryString($opts['query']);
1101 | isset($opts['indexes']) && $this->addIndex($opts['indexes']);
1102 | isset($opts['fetch_field']) && $this->addFetchFields($opts['fetch_field']);
1103 | isset($opts['format']) && $this->setFormat($opts['format']);
1104 | isset($opts['start']) && $this->setStartHit($opts['start']);
1105 | isset($opts['hits']) && $this->setHits((int) $opts['hits']);
1106 | isset($opts['filter']) && $this->addFilter($opts['filter']);
1107 | isset($opts['kvpair']) && $this->setPair($opts['kvpair']);
1108 | isset($opts['rerankSize']) && $this->addRerankSize($opts['rerankSize']);
1109 |
1110 | if ($type == 'search') {
1111 | isset($opts['sort']) && $this->sort = $opts['sort'];
1112 | isset($opts['aggregate']) && $this->aggregate = $opts['aggregate'];
1113 | isset($opts['distinct']) && $this->distinct = $opts['distinct'];
1114 | isset($opts['formula_name']) && $this->setFormulaName($opts['formula_name']);
1115 | isset($opts['summary']) && $this->summary = $opts['summary'];
1116 | isset($opts['qp']) && $this->addQPName($opts['qp']);
1117 | isset($opts['disable_qp']) && $this->addDisabledQP($opts['disable']);
1118 | } else if ($type == 'scroll') {
1119 | isset($opts['scroll_id']) && $this->setScrollId($opts['scroll_id']);
1120 | isset($opts['scroll']) && $this->setScroll($opts['scroll']);
1121 | $this->setSearchType(self::SEARCH_TYPE_SCAN);
1122 | }
1123 | }
1124 | }
1125 |
1126 | /**
1127 | * 生成HTTP的请求串,并通过CloudsearchClient类向API服务发出请求并返回结果。
1128 | *
1129 | * query参数中的query子句和config子句必需的,其它子句可选。
1130 | *
1131 | * @return string
1132 | */
1133 | private function call($type = 'search')
1134 | {
1135 | $haquery = array();
1136 | $haquery[] = "config=" . $this->clauseConfig();
1137 | $haquery[] = "query=" . ($this->getQuery() ? $this->getQuery() : "''") . "";
1138 |
1139 | ($f = $this->getFilter()) && ($haquery[] = 'filter=' . $f);
1140 | ($k = $this->getPair()) && ($haquery[] = 'kvpairs=' . $k);
1141 | if ($type == 'search') {
1142 | ($s = $this->getSortString()) && ($haquery[] = "sort=" . $s);
1143 | ($d = $this->getDistinctString()) && ($haquery[] = 'distinct=' . $d);
1144 | ($a = $this->getAggregateString()) && ($haquery[] = 'aggregate=' . $a);
1145 | }
1146 |
1147 | $params = array(
1148 | 'query' => implode("&&", $haquery),
1149 | 'index_name' => implode(";", $this->getSearchIndexes()),
1150 | 'format' => $this->getFormat(),
1151 | );
1152 |
1153 | if ($result = $this->getCustomParam()) {
1154 | foreach ($result as $k => $v) {
1155 | $params[$k] = $v;
1156 | }
1157 | }
1158 |
1159 | ($f = $this->getFetchFields()) && ($params['fetch_fields'] = implode(";", $f));
1160 | if ($type == 'search') {
1161 | ($f = $this->getFormulaName()) && ($params['formula_name'] = $f);
1162 | ($f = $this->getFirstFormulaName()) && ($params['first_formula_name'] = $f);
1163 | ($s = $this->getSummaryString()) && ($params['summary'] = $s);
1164 | ($f = $this->getQPName()) && ($params['qp'] = implode(",", $f));
1165 | ($f = $this->getDisabledFunctionString()) && ($params['disable'] = $f);
1166 | } else if ($type == 'scroll') {
1167 | ($f = $this->getScroll()) && ($params['scroll'] = $f);
1168 | ($f = $this->getScrollId()) && ($params['scroll_id'] = $f);
1169 | $params['search_type'] = self::SEARCH_TYPE_SCAN;
1170 | }
1171 |
1172 | return $this->client->call($this->path, $params, 'GET');
1173 | }
1174 |
1175 | /**
1176 | * 生成语法的config子句并返回。
1177 | * @return string
1178 | */
1179 | private function clauseConfig()
1180 | {
1181 | $config = array();
1182 | $config[] = 'format:' . $this->getFormat();
1183 | $config[] = 'start:' . $this->getStartHit();
1184 | $config[] = 'hit:' . $this->getHits();
1185 | ($r = $this->getRerankSize()) && ($config[] = 'rerank_size:' . $r);
1186 |
1187 | return implode(",", $config);
1188 | }
1189 | }
1190 |
--------------------------------------------------------------------------------