用于大数据量展示
2024-03-31 02:54:53 2025-02-09 10:55:01 PHP 124 views
sudo apt update
sudo apt install openjdk-17-jdk
java -version
wget -qO - https://artifacts.elastic.co/GPG-KEY-elasticsearch | sudo apt-key add -
sudo sh -c 'echo "deb https://artifacts.elastic.co/packages/8.x/apt stable main" > /etc/apt/sources.list.d/elastic-8.x.list'
sudo apt update && sudo apt install elasticsearch=8.13.0
/etc/elasticsearch/elasticsearch.yml
sudo systemctl daemon-reload sudo systemctl enable elasticsearch.service
composer require elasticsearch/elasticsearch
composer require guzzlehttp/guzzle
php artisan make:provider ElasticsearchServiceProvider
在服务提供者中绑定
<?php
namespace App\Providers;
use Elastic\Elasticsearch\Client;
use Elastic\Elasticsearch\ClientBuilder;
use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Support\ServiceProvider;
class ElasticsearchServiceProvider extends ServiceProvider
{
/**
* Register services.
*/
public function register(): void
{
$this->app->bind(Client::class, function () {
// 明确地创建一个 Guzzle 客户端实例
$guzzleClient = new GuzzleClient();
// 传入 Guzzle 客户端到 Elasticsearch 客户端构建器
return ClientBuilder::create()
->setHttpClient($guzzleClient)
->build();
});
}
/**
* Bootstrap services.
*/
public function boot(): void
{
//
}
}
ELASTICSEARCH_HOST=localhost:9200
设计ES索引结构并创建索引生成命令文件 php artisan make:command CreateOrdersIndex
app/Console/Commands/CreateOrdersIndex
<?php
namespace App\Console\Commands;
use Elastic\Elasticsearch\Client;
use Illuminate\Console\Command;
class CreateOrdersIndex extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'elasticsearch:create-orders-index';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Creates an Elasticsearch index for orders';
// 构造函数中使用类型提示来注入 Elasticsearch 客户端
public function __construct(private Client $elasticsearch)
{
parent::__construct();
}
/**
* Execute the console command.
*/
public function handle()
{
// 获取Elasticsearch客户端实例
$client = $this->elasticsearch;
// 索引名称
$indexName = 'orders';
// 索引结构和设置
$params = [
'index' => 'orders',
'body' => [
'settings' => [
'number_of_shards' => 1,
'number_of_replicas' => 0,
],
'mappings' => [
'properties' => [
'id' => ['type' => 'integer'],
'open_id' => ['type' => 'keyword'],
'order_no' => ['type' => 'keyword'],
'id_card_name' => ['type' => 'keyword'],
'id_card' => ['type' => 'keyword'],
'phonenum' => ['type' => 'keyword'],
'contact' => ['type' => 'keyword'],
'mobile' => ['type' => 'keyword'],
'express_no' => ['type' => 'keyword'],
'pay_num' => ['type' => 'keyword'],
'api_order_no' => ['type' => 'keyword'],
'open_status' => ['type' => 'integer'],
'express_status' => ['type' => 'integer'],
'status' => ['type' => 'integer'],
'hkgj_package_id' => ['type' => 'integer'],
'package_id' => ['type' => 'integer'],
'bus_order_id' => ['type' => 'integer'],
'supplier_id' => ['type' => 'integer'],
'fxuser_id' => ['type' => 'integer'],
'fx2_id' => ['type' => 'integer'],
'tenant_id' => ['type' => 'integer'],
'anchor_id' => ['type' => 'integer'],
'pay_id' => ['type' => 'integer'],
'live_room_id' => ['type' => 'integer'],
'api_lock' => ['type' => 'integer'],
'api_valid' => ['type' => 'integer'],
'api_sync' => ['type' => 'integer'],
'hk_sync' => ['type' => 'integer'],
'cid' => ['type' => 'integer'],
'channe_id' => ['type' => 'integer'],
'admin_user_id' => ['type' => 'integer'],
'pay_status' => ['type' => 'integer'],
'sms_status' => ['type' => 'integer'],
'intercept_status' => ['type' => 'integer'],
'created_at' => [
'type' => 'date',
'format' => 'strict_date_optional_time||epoch_millis'
],
'updated_at' => [
'type' => 'date',
'format' => 'strict_date_optional_time||epoch_millis'
],
'deleted_at' => [
'type' => 'date',
'format' => 'strict_date_optional_time||epoch_millis'
],
'express_address' => ['type' => 'keyword'],
'express_name' => ['type' => 'keyword'],
'idcard_front' => ['type' => 'keyword'],
'idcard_back' => ['type' => 'keyword'],
'user_with_idcard' => ['type' => 'keyword'],
'package_name' => ['type' => 'keyword'],
'remark' => ['type' => 'keyword'],
'intercept_msg' => ['type' => 'keyword'],
'nick' => ['type' => 'keyword'],
'buyer_openid' => ['type' => 'keyword'],
'author_id' => ['type' => 'keyword'],
'author_name' => ['type' => 'keyword'],
'dy_pacage_code' => ['type' => 'keyword'],
'dy_product_id' => ['type' => 'keyword'],
],
],
],
];
// 创建索引
$client->indices()->create($params);
}
}
App\Console\Kernel
的$commands中
注册自定义命令 \App\Console\Commands\CreateOrdersIndex::class
config/app.php
的providers
数组中注册App\Providers\ElasticsearchServiceProvider::class
php artisan elasticsearch:create-orders-index
curl -X GET "localhost:9200/_cat/indices/orders?pretty"
验证是否成功创建导入命令 php artisan make:command ImportOrdersToElasticsearch
app/Console/ImportOrdersToElasticsearch
<?php
namespace App\Console\Commands;
use App\Models\Order;
use Elastic\Elasticsearch\Client;
use Illuminate\Console\Command;
class ImportOrdersToElasticsearch extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'elasticsearch:import-orders';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Imports orders to Elasticsearch';
private Client $elasticsearch;
public function __construct(Client $elasticsearch)
{
parent::__construct();
$this->elasticsearch = $elasticsearch;
}
/**
* Execute the console command.
*/
public function handle()
{
$this->info('Starting to import orders...');
// 获取所有订单
$orders = Order::all();
foreach ($orders as $order) {
$document = [
'index' => 'orders',
'id' => $order->id,
'body' => [
'order_no' => $order->order_no,
'open_status' => (int) $order->open_status,
'open_message' => $order->open_message,
'id_card_name' => $order->id_card_name,
'id_card' => $order->id_card,
'phonenum' => $order->phonenum,
'contact' => $order->contact,
'mobile' => $order->mobile,
'express_address' => $order->express_address,
'express_no' => $order->express_no,
'express_name' => $order->express_name,
'idcard_front' => $order->idcard_front,
'idcard_back' => $order->idcard_back,
'user_with_idcard' => $order->user_with_idcard,
'pay_num' => $order->pay_num,
'express_status' => (int) $order->express_status,
'status' => (int) $order->status,
'hkgj_package_id' => $order->hkgj_package_id,
'package_id' => (int) $order->package_id,
'bus_order_id' => $order->bus_order_id,
'package_name' => $order->package_name,
'supplier_id' => $order->supplier_id,
'fxuser_id' => $order->fxuser_id,
'fx2_id' => $order->fx2_id,
'tenant_id' => $order->tenant_id,
'anchor_id' => $order->anchor_id,
'pay_id' => $order->pay_id,
'live_room_id' => $order->live_room_id,
'created_at' => $order->created_at ? $order->created_at->toIso8601String() : null,
'updated_at' => $order->updated_at ? $order->updated_at->toIso8601String() : null,
'deleted_at' => $order->deleted_at ? $order->deleted_at->toIso8601String() : null,
'api_order_no' => $order->api_order_no,
'api_lock' => (int) $order->api_lock,
'api_valid' => (int) $order->api_valid,
'api_sync' => (int) $order->api_sync,
'hk_sync' => (int) $order->hk_sync,
'remark' => $order->remark,
'cid' => $order->cid,
'open_id' => $order->open_id,
'channe_id' => $order->channe_id,
'admin_user_id' => $order->admin_user_id,
'pay_status' => (int) $order->pay_status,
'refund_text' => $order->refund_text,
'sms_status' => (int) $order->sms_status,
'intercept_status' => (int) $order->intercept_status,
'intercept_msg' => $order->intercept_msg,
'nick' => $order->nick,
'buyer_openid' => $order->buyer_openid,
'author_id' => $order->author_id,
'author_name' => $order->author_name,
'dy_pacage_code' => $order->dy_pacage_code,
'dy_product_id' => $order->dy_product_id,
// 添加更多字段...
],
];
$this->elasticsearch->index($document);
}
$this->info('All orders have been imported to Elasticsearch.');
}
}
App\Console\Kernel
的$commands中
注册自定义命令 \App\Console\Commands\CreateOrdersIndex::class
/etc/elasticsearch/jvm.options
的-Xms2g -Xmx2g/etc/elasticsearch/elasticsearch.yml
文件最后一行写入indices.memory.index_buffer_size: 10%
sudo systemctl restart elasticsearch.service
php artisan elasticsearch:import-orders
封装service服务层用于查询
app\service\ElasticsearchService.php
<?php
namespace App\Services;
use Elastic\Elasticsearch\ClientBuilder;
use Elastic\Elasticsearch\Client;
class ElasticsearchService
{
protected $client;
public function __construct()
{
$this->client = ClientBuilder::create()->build();
}
public function searchOrders($criteria)
{
$pageSize = $criteria['limit'] ?? 10; // 如果未提供,默认每页显示10条
$params = [
'index' => 'orders',
'size' => $pageSize,
'track_total_hits' => true, // 确保总命中数计算准确
];
// 如果提供了lastSortValues,则使用它进行search_after分页
if (isset($criteria['direction'])== 'next' && isset($criteria['last_sort_values'])) {
$params['body']['search_after'][] = $criteria['last_sort_values'];
}
// 初始化一个数组来收集范围查询条件
$rangeFilters = [];
foreach ($criteria as $field => $value) {
if (!empty($value)) {
switch ($field) {
case 'open_id':
$params['body']['query']['bool']['filter'][] = [
'terms' => [
$field => is_array($value) ? $value : [$value],
],
];
break;
case 'order_no':
case 'id_card_name':
case 'id_card':
case 'phonenum':
case 'contact':
case 'mobile':
case 'express_no':
case 'pay_num':
case 'api_order_no':
case 'open_status':
case 'express_status':
case 'status':
case 'hkgj_package_id':
case 'package_id':
case 'bus_order_id':
case 'supplier_id':
case 'fxuser_id':
case 'fx2_id':
case 'tenant_id':
case 'anchor_id':
case 'pay_id':
case 'live_room_id':
case 'api_lock':
case 'api_valid':
case 'api_sync':
case 'hk_sync':
case 'cid':
case 'channe_id':
case 'admin_user_id':
case 'pay_status':
case 'sms_status':
case 'intercept_status':
$params['body']['query']['bool']['filter'][] = [
'term' => [
$field => $value,
],
];
break;
case 'open_message':
case 'express_address':
case 'express_name':
case 'idcard_front':
case 'idcard_back':
case 'user_with_idcard':
case 'package_name':
case 'remark':
case 'intercept_msg':
case 'nick':
case 'buyer_openid':
case 'author_id':
case 'author_name':
case 'dy_pacage_code':
case 'dy_product_id':
$params['body']['query']['bool']['must'][] = [
'wildcard' => [
$field => "*{$value}*",
],
];
break;
case 'created_at_start':
case 'updated_at_start':
case 'deleted_at_start':
case 'created_at_end':
case 'updated_at_end':
case 'deleted_at_end':
// 提取实际的字段名
$realField = str_replace(['_start', '_end'], '', $field);
// 确定是使用gte还是lte操作符
$operator = str_contains($field, '_start') ? 'gte' : 'lte';
// 如果之前没有为这个字段创建过范围过滤器,就初始化一个
if (!isset($rangeFilters[$realField])) {
$rangeFilters[$realField] = [];
}
// 将这个条件添加到相应字段的范围过滤器中
$rangeFilters[$realField][$operator] = $value;
break;
case 'order':
$orderDirection = $value === "ascending" ? "asc" : "desc";
$params['body']['sort'] = [
['id' => ['order' => $orderDirection]] // 注意这里的改动
];
break;
}
}
}
// 现在将所有收集到的范围过滤器添加到查询中
foreach ($rangeFilters as $field => $rangeFilter) {
$params['body']['query']['bool']['filter'][] = [
'range' => [
$field => $rangeFilter,
],
];
}
// print_r($params);die;
// 执行搜索查询
$response = $this->client->search($params);
// 准备返回结构,包括下一页和上一页所需的search_after值
$items = $response['hits']['hits'];
$nextPageSortValues = end($items)['sort'][0] ?? null; // 获取下一页的search_after值
$firstPageSortValues = $items[0]['sort'][0] ?? null; // 获取上一页的search_after值(如果可行)
// 准备返回结构
return [
'items' => $items,
'nextPageSortValues' => $nextPageSortValues,
'firstPageSortValues' => $criteria['order'] === "ascending" ? $firstPageSortValues-1 : $firstPageSortValues+1,
'totalHits' => $response['hits']['total']['value'],
];
}
}
控制器中调用服务层
app/http/admin/ordercontroller
protected $elasticsearchService;
public function __construct(ElasticsearchService $elasticsearchService)
{
$this->elasticsearchService = $elasticsearchService;
}
public function lists(GetESListRequest $request): Response
{
$validated = $request->validated();
//获取所有店铺ID
$admin_id = Auth::guard('admin')->user()->id;
$store_ids = Admin::getStores($admin_id);
if(empty($store_ids)){
return ResponseBuilder::asError(ApiCode::HTTP_BAD_REQUEST)
->withHttpCode(ApiCode::HTTP_BAD_REQUEST)
->withMessage('联系管理员')
->build();
}
$open_ids = Store::whereIn('id', $store_ids)->pluck('unique_identifier')->toArray();
if (isset($validated['open_id'])) {
if (!in_array($validated['open_id'], $open_ids)) {
return ResponseBuilder::asError(ApiCode::HTTP_BAD_REQUEST)
->withHttpCode(ApiCode::HTTP_BAD_REQUEST)
->withMessage('open_id不在允许范围内')
->build();
}
$validated['open_id'] = [$validated['open_id']];
} else {
$validated['open_id'] = $open_ids;
}
//格式化时间
$dateFields = ['created_at_start', 'created_at_end', 'updated_at_start', 'updated_at_end', 'deleted_at_start', 'deleted_at_end'];
foreach ($dateFields as $field) {
if (isset($validated[$field])) {
$timeZone = new \DateTimeZone('Asia/Shanghai'); // 设置时区为东八区,您可以根据实际情况修改时区
$dateTime = new \DateTime($validated[$field], $timeZone); // 创建 DateTime 对象并指定时区
$validated[$field] = $dateTime->format(\DateTime::ATOM); // 格式化为带时区信息的 ISO 8601 格式
}
}
// 分页参数,例如
$validated['limit'] = $request->input('limit', 10); // 默认每页10条
$validated['offset'] = $request->input('offset', 1); // 默认第1页
//return response()->json($datas);
// 调用服务层执行查询
$result = $this->elasticsearchService->searchOrders($validated); // 假设这是调用服务层的方法
// 返回数据及分页信息
return ResponseBuilder::asSuccess(ApiCode::HTTP_OK)
->withHttpCode(ApiCode::HTTP_OK)
->withData([
'data' => $result['items'], // 实际的数据项
'total' => $result['totalHits'], // 总命中数
'nextPageSortValues' => $result['nextPageSortValues'], // 下一页起始值
'firstPageSortValues' => $result['firstPageSortValues'], // 上一页起始值
])
->withMessage(__('message.common.search.success'))
->build();
}
模型封装统计方法(也可以封装到服务层)
app/models/order
public static function reportFormsEs($validated) {
$client = ClientBuilder::create()->setHosts(['localhost:9200'])->build(); // 配置您的Elasticsearch客户端
$params = [
'index' => 'orders', // 订单索引名称
'body' => [
'query' => [
'bool' => [
'must' => [
['terms' => ['open_id' => $validated['open_ids']]],
['range' => ['created_at' => [
'gte' => date('Y-m-d', strtotime("-31 days")),
'lte' => date('Y-m-d'),
'format'=> 'yyyy-MM-dd' // 确保日期格式正确
]]]
]
]
],
'aggs' => [
'orders_per_day' => [
'date_histogram' => [
'field' => 'created_at',
'calendar_interval' => 'day', // 按天聚合
'format' => 'yyyy-MM-dd', // 输出格式
"time_zone"=> "+08:00" // 指定时区
],
'aggs' => [
'order_count' => ['value_count' => ['field' => 'id']] // 计数每天的订单
]
]
],
'size' => 0 // 不需要原始文档,只需要聚合结果
]
];
$response = $client->search($params); // 执行查询
$buckets = $response['aggregations']['orders_per_day']['buckets']; // 获取聚合数据
$reportForms = collect($buckets)->map(function($bucket) {
return [
'date' => $bucket['key_as_string'], // 日期
'value' => $bucket['order_count']['value'] // 当天的订单数
];
});
return ['data' => $reportForms];
}
19.控制器调用封装方法
public function reportFormsES()
{
$admin_id = Auth::guard('admin')->user()->id;
$store_ids = Admin::getStores($admin_id);
if(empty($store_ids)){
return ResponseBuilder::asError(ApiCode::HTTP_BAD_REQUEST)
->withHttpCode(ApiCode::HTTP_BAD_REQUEST)
->withMessage('联系管理员')
->build();
}
$open_ids = Store::whereIn('id', $store_ids)->pluck('unique_identifier');
$validated['open_ids'] = $open_ids;
return ResponseBuilder::asSuccess(ApiCode::HTTP_OK)
->withHttpCode(ApiCode::HTTP_OK)
->withData(Order::reportFormsEs($validated)) // 使用Elasticsearch
->withMessage(__('message.common.search.success'))
->build();
}
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
noauth
暂无预执行脚本
暂无后执行脚本
暂无描述
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
noauth
暂无预执行脚本
暂无后执行脚本
暂无描述
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
noauth
暂无预执行脚本
暂无后执行脚本
暂无描述
已完成
POST
form-data
参数名 | 示例值 | 参数类型 | 是否必填 | 参数描述 |
---|---|---|---|---|
order_no | 订单号, | String | 是 | - |
open_status | 0 | Integer | 是 | - |
open_message | 开卡回文, | String | 是 | - |
id_card_name | 身份证姓名, | String | 是 | - |
id_card | 身份证号码, | String | 是 | - |
phonenum | 开卡号码, | String | 是 | - |
contact | 收货人姓名, | String | 是 | - |
mobile | 收货手机号, | String | 是 | - |
express_address | 收货地址, | String | 是 | - |
express_no | 物流单号, | String | 是 | - |
express_name | 物流公司名称, | String | 是 | - |
pay_num | 支付金额, | String | 是 | - |
express_status | - | Integer | 是 | - |
hkgj_package_id | 0 | Integer | 是 | - |
package_name | 中国电信无线流量卡星卡本地卡20年套餐5G手机卡电话卡全国通 | String | 是 | - |
remark | - | String | 是 | - |
pay_status | 1 | Integer | 是 | - |
author_id | 5255460022941408 | String | 是 | - |
author_name | 1 | String | 是 | - |
dy_pacage_code | krR7S9CQCZ | String | 是 | - |
dy_product_id | Ggape premium | String | 是 | - |
open_id | 1 | Integer | 是 | - |
created_at_start | 2024-03-02 15:23:41 | String | 是 | - |
created_at_end | 2024-03-03 15:23:50 | String | 是 | - |
limit | 50 | String | 是 | - |
order | descending | String | 是 | - |
direction | netx | String | 是 | 值为next开启翻页 第一次获取首页或者第一次获取带条件的不需要开 后续上一页下一页需要 |
last_sort_values | 1031001 | String | 是 | 分页开始值 根据返回的nextPageSortValues(下一页数据开始值)和firstPageSortValues(上一页数据开始值)修改 |
bearer
暂无预执行脚本
暂无后执行脚本
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
noauth
暂无预执行脚本
暂无后执行脚本
暂无描述
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
noauth
暂无预执行脚本
暂无后执行脚本
暂无描述
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
参数名 | 示例值 | 参数描述 |
---|
暂无参数
noauth
暂无预执行脚本
暂无后执行脚本
暂无描述
已完成
POST
form-data
bearer
暂无预执行脚本
暂无后执行脚本