refactor: 产品兼容性导出
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
This commit is contained in:
@@ -148,6 +148,84 @@ if (!function_exists('xlsx_writer')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!function_exists('xlsx_download_xaccel')) {
|
||||||
|
/**
|
||||||
|
* 构建Excel数据(采用Nginx Sendfile方式)
|
||||||
|
*
|
||||||
|
* Nginx中需配置
|
||||||
|
* location /download/ {
|
||||||
|
* internal; # 关键:禁止外部直接访问这个路径
|
||||||
|
* alias /tmp/; # 使用 alias,将 URI 映射到你存放文件的真实目录
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* 注意:避免临时文件造成磁盘堆积,需依赖系统层定时清理(如 systemd-tmpfiles / cron),PHP 侧不做删除
|
||||||
|
* 如:# 每分钟执行一次,删除 /tmp 下 10 分钟前创建的 xlsx_ 临时文件
|
||||||
|
* * * * * * find /tmp -maxdepth 1 -name 'ow_xlsx_*' -type f -mmin +10 -delete
|
||||||
|
*
|
||||||
|
* @param array|\think\model\Collection|Generator $data 数据 例:[['name'=>'张三','age'=>18, 'sex'=>'男']]
|
||||||
|
* @param array $schema 表头信息 例:['name' => '姓名', 'age' => '年龄', 'sex' => '性别']
|
||||||
|
* @param string $filename 下载文件名称 默认为YmdHis时间
|
||||||
|
* @return \think\Response
|
||||||
|
*/
|
||||||
|
function xlsx_download_xaccel(array|\think\model\Collection|Generator $data, array $schema, string $filename = ''): \think\Response
|
||||||
|
{
|
||||||
|
// 生成到临时文件,避免将整个文件内容写入输出缓冲区
|
||||||
|
$tmp_file = tempnam(sys_get_temp_dir(), 'ow_xlsx_');
|
||||||
|
if (false === $tmp_file) {
|
||||||
|
throw new \RuntimeException('创建临时文件失败');
|
||||||
|
}
|
||||||
|
// tempnam 默认 0600,需放开读权限,否则 Nginx 与 PHP-FPM 用户不同时无法读取
|
||||||
|
chmod($tmp_file, 0644);
|
||||||
|
|
||||||
|
// 获取Spreadsheet对象
|
||||||
|
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
|
||||||
|
$sheet = $spreadsheet->getActiveSheet();
|
||||||
|
|
||||||
|
// 写入表头
|
||||||
|
$title_col = 'A';
|
||||||
|
foreach (array_values($schema) as $value) {
|
||||||
|
$sheet->setCellValue($title_col . '1', $value);
|
||||||
|
$title_col++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入数据
|
||||||
|
$row = 2;
|
||||||
|
$keys = array_keys($schema);
|
||||||
|
foreach ($data as $item) {
|
||||||
|
$data_col = 'A';
|
||||||
|
foreach ($keys as $key) {
|
||||||
|
$sheet->setCellValue($data_col . $row, $item[$key] ?? '');
|
||||||
|
$data_col++;
|
||||||
|
}
|
||||||
|
$row++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 写入临时文件,不预计算公式以降低耗时与内存
|
||||||
|
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
|
||||||
|
$writer->setPreCalculateFormulas(false);
|
||||||
|
$writer->save($tmp_file);
|
||||||
|
|
||||||
|
// 释放Spreadsheet内存
|
||||||
|
$spreadsheet->disconnectWorksheets();
|
||||||
|
unset($spreadsheet, $writer, $sheet);
|
||||||
|
|
||||||
|
if ('' == $filename) $filename = date('YmdHis');
|
||||||
|
$filename = urlencode($filename);
|
||||||
|
|
||||||
|
$response = \think\Response::create()
|
||||||
|
->header([
|
||||||
|
'Access-Control-Expose-Headers' => 'Content-Disposition',
|
||||||
|
'Content-Type' => 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet; charset=UTF-8',
|
||||||
|
'Content-Disposition' => 'attachment;filename=' . $filename . '.xlsx',
|
||||||
|
'Cache-Control' => 'max-age=0',
|
||||||
|
'Content-Length' => filesize($tmp_file),
|
||||||
|
'X-Accel-Redirect' => '/download/' . basename($tmp_file), // Nginx
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!function_exists('strtobytes')) {
|
if (!function_exists('strtobytes')) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -318,15 +318,7 @@ class ProductCompat
|
|||||||
'language_id',
|
'language_id',
|
||||||
'deleted_at'
|
'deleted_at'
|
||||||
])
|
])
|
||||||
->with([
|
->language(1)
|
||||||
'types' => function ($query) {
|
|
||||||
$query->field(['id', 'name']);
|
|
||||||
},
|
|
||||||
'parts' => function ($query) {
|
|
||||||
$query->field(['id', 'name']);
|
|
||||||
}
|
|
||||||
])
|
|
||||||
->language(request()->lang_id)
|
|
||||||
->where(function ($query) use($params) {
|
->where(function ($query) use($params) {
|
||||||
if (!empty($params['type_id'])) {
|
if (!empty($params['type_id'])) {
|
||||||
$query->typeId($params['type_id']);
|
$query->typeId($params['type_id']);
|
||||||
@@ -341,6 +333,10 @@ class ProductCompat
|
|||||||
'compatible_model' => $params['compatible_model'] ?? null,
|
'compatible_model' => $params['compatible_model'] ?? null,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// 类型/配件为字典小表,一次性载入内存映射,避免每批预加载关联查询
|
||||||
|
$type_map = ProductCompatTypesModel::column('name', 'id');
|
||||||
|
$part_map = ProductCompatPartsModel::column('name', 'id');
|
||||||
|
|
||||||
$schema = [
|
$schema = [
|
||||||
'id' => 'id',
|
'id' => 'id',
|
||||||
'type_name' => '产品类型',
|
'type_name' => '产品类型',
|
||||||
@@ -359,9 +355,9 @@ class ProductCompat
|
|||||||
'updated_at' => '更新时间'
|
'updated_at' => '更新时间'
|
||||||
];
|
];
|
||||||
|
|
||||||
return xlsx_writer($this->xlsxRowGenerator($query), $schema, '产品兼容性' . date('YmdHis'));
|
return xlsx_download_xaccel($this->xlsxRowGenerator($query, $type_map, $part_map), $schema, '产品兼容性' . date('YmdHis'));
|
||||||
}
|
}
|
||||||
private function xlsxRowGenerator(Query $query, int $page_size = 500): Generator
|
private function xlsxRowGenerator(Query $query, array $type_map, array $part_map, int $page_size = 500): Generator
|
||||||
{
|
{
|
||||||
// 主键游标分页,避免 offset 深翻页的性能问题
|
// 主键游标分页,避免 offset 深翻页的性能问题
|
||||||
$last_id = 0;
|
$last_id = 0;
|
||||||
@@ -392,10 +388,10 @@ class ProductCompat
|
|||||||
foreach ($items as $item) {
|
foreach ($items as $item) {
|
||||||
yield [
|
yield [
|
||||||
'id' => $item['id'],
|
'id' => $item['id'],
|
||||||
'type_name' => $item['types']['name'] ?? '',
|
'type_name' => $type_map[$item['type_id']] ?? '',
|
||||||
'brand_name' => $item['brand_name'],
|
'brand_name' => $item['brand_name'],
|
||||||
'model' => $item['model'],
|
'model' => $item['model'],
|
||||||
'part_name' => $item['parts']['name'] ?? '',
|
'part_name' => $part_map[$item['part_id']] ?? '',
|
||||||
'level_name' => $item['level_name'],
|
'level_name' => $item['level_name'],
|
||||||
'spec_name' => $item['spec_name'],
|
'spec_name' => $item['spec_name'],
|
||||||
'series_name' => $item['series_name'],
|
'series_name' => $item['series_name'],
|
||||||
|
|||||||
Reference in New Issue
Block a user