All checks were successful
Gitea Actions Official-website / deploy-dev (push) Successful in 4s
266 lines
9.6 KiB
PHP
266 lines
9.6 KiB
PHP
<?php
|
||
// 这是系统自动生成的公共文件
|
||
|
||
if (!function_exists('xlsx_reader')) {
|
||
/**
|
||
* 读取Excel数据
|
||
* @param string $file Excel文件路径
|
||
* @param int $initial_row 初始行
|
||
* @param array $keys_map 列 -> 键 例:['A' => 'name', 'B' => 'age', 'C' => 'sex']
|
||
* @return array
|
||
*/
|
||
function xlsx_reader(string $file, int $initial_row = 1, array $keys_map = [], bool $read_only = false): array
|
||
{
|
||
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
|
||
if ($read_only) {
|
||
// 只读模式,不读取公式,样式等等,减少内存消耗
|
||
$reader->setReadDataOnly(true);
|
||
}
|
||
|
||
// 读取文件
|
||
$spreadsheet = $reader->load($file);
|
||
|
||
// 获取活动sheet
|
||
$sheet = $spreadsheet->getActiveSheet();
|
||
|
||
// 获取最大行
|
||
$max_row = $sheet->getHighestRow();
|
||
|
||
// 获取最大列
|
||
$max_col = $sheet->getHighestColumn();
|
||
|
||
$xlsx_data = [];
|
||
$first_col = ord('A');
|
||
$last_col = ord($max_col);
|
||
for ($row = $initial_row; $row <= $max_row; $row++) {
|
||
for ($col = $first_col; $col <= $last_col; $col++) {
|
||
$key = $col_chr = chr($col);
|
||
if (!empty($keys_map)) {
|
||
$key = $keys_map[$col_chr] ?? $col_chr;
|
||
}
|
||
$xlsx_data[$row][$key] = $sheet->getCell($col_chr . $row)->getValue();
|
||
}
|
||
}
|
||
|
||
return $xlsx_data;
|
||
}
|
||
}
|
||
|
||
if (!function_exists('xlsx_stream_reader')) {
|
||
/**
|
||
* 读取Excel数据
|
||
* @param string $file Excel文件路径
|
||
* @param int $initial_row 初始行
|
||
* @param array $keys_map 列 -> 键 例:['A' => 'name', 'B' => 'age', 'C' => 'sex']
|
||
* @return Generator
|
||
*/
|
||
function xlsx_stream_reader(string $file, int $initial_row = 1, array $keys_map = [], bool $read_only = false): Generator
|
||
{
|
||
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReader('Xlsx');
|
||
if ($read_only) {
|
||
// 只读模式,不读取公式,样式等等,减少内存消耗
|
||
$reader->setReadDataOnly(true);
|
||
}
|
||
|
||
// 读取文件
|
||
$spreadsheet = $reader->load($file);
|
||
|
||
// 获取活动sheet
|
||
$sheet = $spreadsheet->getActiveSheet();
|
||
|
||
// 获取最大行
|
||
$max_row = $sheet->getHighestRow();
|
||
|
||
// 获取最大列
|
||
$max_col = $sheet->getHighestColumn();
|
||
|
||
$xlsx_row = [];
|
||
$first_col = ord('A');
|
||
$last_col = ord($max_col);
|
||
for ($row = $initial_row; $row <= $max_row; $row++) {
|
||
for ($col = $first_col; $col <= $last_col; $col++) {
|
||
$key = $col_chr = chr($col);
|
||
if (!empty($keys_map)) {
|
||
$key = $keys_map[$col_chr] ?? $col_chr;
|
||
}
|
||
$xlsx_row[$key] = $sheet->getCell($col_chr . $row)->getValue();
|
||
}
|
||
yield $xlsx_row;
|
||
}
|
||
|
||
// 主动销毁对象以释放内存
|
||
$spreadsheet->disconnectWorksheets();
|
||
unset($spreadsheet);
|
||
}
|
||
}
|
||
|
||
if (!function_exists('xlsx_writer')) {
|
||
/**
|
||
* 构建Excel数据
|
||
* @param array $data 数据 例:[['name'=>'张三','age'=>18, 'sex'=>'男']]
|
||
* @param array $schema 表头信息 例:['name' => '姓名', 'age' => '年龄', 'sex' => '性别']
|
||
* @param string $filename 下载文件名称 默认为YmdHis时间
|
||
* @param string $output 输出方式 php://output 或 文件路径
|
||
* @return \think\Response
|
||
*/
|
||
function xlsx_writer(array|\think\model\Collection|Generator $data, array $schema, string $filename = '', string $output = 'php://output'): \think\Response
|
||
{
|
||
// 获取Spreadsheet对象
|
||
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
|
||
$sheet = $spreadsheet->getActiveSheet();
|
||
|
||
// 写入表头
|
||
$title = array_values($schema);
|
||
$title_col = 'A';
|
||
foreach ($title 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++;
|
||
}
|
||
|
||
flush();
|
||
ob_flush();
|
||
if ($filename == '') $filename = date('YmdHis');
|
||
$filename = urlencode($filename);
|
||
// 文件写入缓冲区
|
||
\PhpOffice\PhpSpreadsheet\IOFactory::createWriter($spreadsheet, 'Xlsx')->save($output);
|
||
// 从缓冲区获取导出文件,并通过Response返回(解决thinkphp框架的Response无法获取设置的header问题)
|
||
$file_data = ob_get_clean();
|
||
$response = \think\Response::create($file_data)->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'
|
||
]);
|
||
return $response;
|
||
}
|
||
}
|
||
|
||
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')) {
|
||
|
||
/**
|
||
* 字符串转字节
|
||
* @param string $str_size 字符串 例:'1MB' '2GB'
|
||
* @return int
|
||
*/
|
||
function strtobytes(string $str_size): int
|
||
{
|
||
preg_match('/(\d+)(\w+)/', $str_size, $matches);
|
||
if (!isset($matches[1])) {
|
||
throw new \Exception('请确认size大小');
|
||
}
|
||
if (!isset($matches[2])) {
|
||
throw new \Exception('请确认单位');
|
||
}
|
||
|
||
$size = intval($matches[1]);
|
||
$unit = $matches[2];
|
||
switch (strtolower($unit)) {
|
||
case 'kb':
|
||
$size *= 1024;
|
||
break;
|
||
case 'mb':
|
||
$size *= 1024 * 1024;
|
||
break;
|
||
case 'gb':
|
||
$size *= 1024 * 1024 * 1024;
|
||
break;
|
||
case 'tb':
|
||
$size *= 1024 * 1024 * 1024 * 1024;
|
||
default:
|
||
throw new \Exception('不支持[' . $unit . ']单位');
|
||
}
|
||
return $size;
|
||
}
|
||
}
|