本文目录导读:

我来为您设计一个完整的PHP导入导出工具封装,支持Excel、CSV格式,并提供灵活的配置选项。
基础导出导入类
<?php
/**
* 数据导出导入工具类
* 支持Excel(需要PHPExcel/PHPOffice)、CSV格式
*/
class DataExportImport {
private $config;
private $errors = [];
/**
* 构造函数
* @param array $config 配置选项
*/
public function __construct($config = []) {
$this->config = array_merge([
'export_path' => __DIR__ . '/exports/',
'import_path' => __DIR__ . '/imports/',
'max_file_size' => 10485760, // 10MB
'allowed_extensions' => ['xls', 'xlsx', 'csv'],
'chunk_size' => 1000, // 分批处理数量
'time_limit' => 300, // 最大执行时间(秒)
], $config);
// 设置PHP执行时间
if ($this->config['time_limit']) {
set_time_limit($this->config['time_limit']);
}
// 创建目录
$this->createDirectories();
}
/**
* 创建必要的目录
*/
private function createDirectories() {
foreach (['export_path', 'import_path'] as $path) {
if (!is_dir($this->config[$path])) {
mkdir($this->config[$path], 0755, true);
}
}
}
/**
* 导出数据到Excel
* @param array $headers 表头
* @param array $data 数据
* @param string $filename 文件名
* @return string|false 文件路径或false
*/
public function exportToExcel($headers, $data, $filename = '') {
try {
// 检查PHPExcel类是否存在
if (!class_exists('PHPExcel') && !class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) {
throw new Exception('请安装 PHPExcel 或 PhpOffice\PhpSpreadsheet 库');
}
$filename = $filename ?: date('YmdHis') . '_export';
$filename = $this->checkFilename($filename);
if (class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) {
// 使用 PhpSpreadsheet
$spreadsheet = new \PhpOffice\PhpSpreadsheet\Spreadsheet();
$sheet = $spreadsheet->getActiveSheet();
// 写入表头
foreach ($headers as $key => $value) {
$sheet->setCellValueByColumnAndRow($key + 1, 1, $value);
}
// 写入数据
$rowNum = 2;
foreach ($data as $row) {
foreach ($row as $colNum => $value) {
$sheet->setCellValueByColumnAndRow($colNum + 1, $rowNum, $value);
}
$rowNum++;
}
// 保存文件
$writer = new \PhpOffice\PhpSpreadsheet\Writer\Xlsx($spreadsheet);
$filePath = $this->config['export_path'] . $filename . '.xlsx';
$writer->save($filePath);
// 自动下载
if (php_sapi_name() !== 'cli') {
$this->downloadFile($filePath, true);
}
return $filePath;
} else {
// 使用 PHPExcel
$this->exportWithPHPExcel($headers, $data, $filename);
}
} catch (Exception $e) {
$this->errors[] = '导出失败: ' . $e->getMessage();
return false;
}
}
/**
* 导出数据到CSV
* @param array $headers 表头
* @param array $data 数据
* @param string $filename 文件名
* @param string $delimiter 分隔符
* @return string|false 文件路径或false
*/
public function exportToCSV($headers, $data, $filename = '', $delimiter = ',') {
try {
$filename = $filename ?: date('YmdHis') . '_export';
$filename = $this->checkFilename($filename);
$filePath = $this->config['export_path'] . $filename . '.csv';
// 打开文件流
$file = fopen($filePath, 'w');
// 添加BOM,防止中文乱码
fwrite($file, "\xEF\xBB\xBF");
// 写入表头
if (!empty($headers)) {
fputcsv($file, $headers, $delimiter);
}
// 写入数据
foreach ($data as $row) {
fputcsv($file, $row, $delimiter);
}
fclose($file);
// 自动下载
if (php_sapi_name() !== 'cli') {
$this->downloadFile($filePath, true);
}
return $filePath;
} catch (Exception $e) {
$this->errors[] = '导出失败: ' . $e->getMessage();
return false;
}
}
/**
* 使用PHPExcel导出
* @param array $headers
* @param array $data
* @param string $filename
*/
private function exportWithPHPExcel($headers, $data, $filename) {
$objPHPExcel = new PHPExcel();
$sheet = $objPHPExcel->getActiveSheet();
// 设置表头
foreach ($headers as $key => $value) {
$sheet->setCellValueByColumnAndRow($key, 1, $value);
}
// 写入数据
$rowNum = 2;
foreach ($data as $row) {
foreach ($row as $colNum => $value) {
$sheet->setCellValueByColumnAndRow($colNum, $rowNum, $value);
}
$rowNum++;
}
// 创建Excel5写入器
$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel5');
$filePath = $this->config['export_path'] . $filename . '.xls';
$objWriter->save($filePath);
if (php_sapi_name() !== 'cli') {
$this->downloadFile($filePath, true);
}
return $filePath;
}
/**
* 导入Excel文件
* @param string $file 文件路径
* @param array $mapping 字段映射
* @return array|false 数据数组或false
*/
public function importFromExcel($file, $mapping = []) {
try {
// 文件校验
$this->validateFile($file);
$spreadsheet = null;
if (class_exists('PhpOffice\PhpSpreadsheet\Spreadsheet')) {
$reader = \PhpOffice\PhpSpreadsheet\IOFactory::createReaderForFile($file);
$spreadsheet = $reader->load($file);
$rows = $spreadsheet->getActiveSheet()->toArray();
} else {
// 使用PHPExcel
$objPHPExcel = PHPExcel_IOFactory::load($file);
$rows = $objPHPExcel->getActiveSheet()->toArray();
}
if (empty($rows)) {
return [];
}
// 获取表头
$headers = array_shift($rows);
// 处理数据
$data = [];
foreach ($rows as $rowIndex => $row) {
$item = [];
foreach ($headers as $colIndex => $header) {
$key = $header;
// 如果有映射,则使用映射后的字段名
if (!empty($mapping) && isset($mapping[$header])) {
$key = $mapping[$header];
}
$item[$key] = isset($row[$colIndex]) ? $row[$colIndex] : '';
}
$data[] = $item;
}
return $data;
} catch (Exception $e) {
$this->errors[] = '导入失败: ' . $e->getMessage();
return false;
}
}
/**
* 导入CSV文件
* @param string $file 文件路径
* @param array $mapping 字段映射
* @param string $delimiter 分隔符
* @return array|false 数据数组或false
*/
public function importFromCSV($file, $mapping = [], $delimiter = ',') {
try {
// 文件校验
$this->validateFile($file, ['csv']);
$data = [];
$handle = fopen($file, 'r');
// 读取BOM
$bom = fread($handle, 3);
if ($bom != "\xEF\xBB\xBF") {
rewind($handle);
}
// 获取表头
$headers = fgetcsv($handle, 0, $delimiter);
// 处理数据
while (($row = fgetcsv($handle, 0, $delimiter)) !== false) {
$item = [];
foreach ($headers as $colIndex => $header) {
$key = $header;
// 如果有映射,则使用映射后的字段名
if (!empty($mapping) && isset($mapping[$header])) {
$key = $mapping[$header];
}
$item[$key] = isset($row[$colIndex]) ? $row[$colIndex] : '';
}
$data[] = $item;
}
fclose($handle);
return $data;
} catch (Exception $e) {
$this->errors[] = '导入失败: ' . $e->getMessage();
return false;
}
}
/**
* 上传文件
* @param array $file $_FILES中的文件数组
* @return string|false 上传后的文件路径或false
*/
public function uploadFile($file) {
try {
// 检查上传文件
if (!isset($file['error']) || is_array($file['error'])) {
throw new Exception('上传文件错误');
}
// 检查错误码
switch ($file['error']) {
case UPLOAD_ERR_OK:
break;
case UPLOAD_ERR_NO_FILE:
throw new Exception('没有文件被上传');
case UPLOAD_ERR_INI_SIZE:
case UPLOAD_ERR_FORM_SIZE:
throw new Exception('文件大小超出限制');
default:
throw new Exception('未知错误');
}
// 检查文件大小
if ($file['size'] > $this->config['max_file_size']) {
throw new Exception('文件大小超出限制');
}
// 生成唯一文件名
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$filename = date('YmdHis') . '_' . uniqid() . '.' . $extension;
$targetPath = $this->config['import_path'] . $filename;
// 移动文件
if (move_uploaded_file($file['tmp_name'], $targetPath)) {
return $targetPath;
} else {
throw new Exception('文件上传失败');
}
} catch (Exception $e) {
$this->errors[] = '上传失败: ' . $e->getMessage();
return false;
}
}
/**
* 校验文件
* @param string $file 文件路径
* @param array $allowedExtensions 允许的扩展名
*/
public function validateFile($file, $allowedExtensions = null) {
if (!file_exists($file)) {
throw new Exception('文件不存在');
}
if (!is_readable($file)) {
throw new Exception('文件不可读');
}
$allowedExtensions = $allowedExtensions ?: $this->config['allowed_extensions'];
$extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions)) {
throw new Exception('文件类型不允许');
}
return true;
}
/**
* 分批导出大文件
* @param callable $callback 数据获取回调函数
* @param callable $processCallback 处理回调(可选)
* @param string $filename 文件名
* @return string|false
*/
public function exportLargeData($callback, $processCallback = null, $filename = '') {
try {
$filename = $filename ?: date('YmdHis') . '_large_export';
$filePath = $this->config['export_path'] . $filename . '.csv';
$file = fopen($filePath, 'w');
fwrite($file, "\xEF\xBB\xBF"); // 添加BOM
$page = 1;
while (true) {
// 获取数据
$data = call_user_func($callback, $page, $this->config['chunk_size']);
if (empty($data)) {
break;
}
// 是否有处理函数
if ($processCallback) {
$data = call_user_func($processCallback, $data);
}
// 写入数据
foreach ($data as $row) {
fputcsv($file, $row);
}
$page++;
}
fclose($file);
return $filePath;
} catch (Exception $e) {
$this->errors[] = '导出失败: ' . $e->getMessage();
return false;
}
}
/**
* 分批导入大文件
* @param string $file 文件路径
* @param callable $processCallback 处理每行数据的回调函数
* @return int|false 处理的行数或false
*/
public function importLargeData($file, $processCallback) {
try {
// 文件校验
$this->validateFile($file, ['csv']);
$handle = fopen($file, 'r');
$rowCount = 0;
// 读取BOM
$bom = fread($handle, 3);
if ($bom != "\xEF\xBB\xBF") {
rewind($handle);
}
// 跳过表头
fgetcsv($handle, 0, ',');
// 逐行处理
while (($row = fgetcsv($handle, 0, ',')) !== false) {
if (!empty($row)) {
// 调用处理函数
$result = call_user_func($processCallback, $row);
if ($result === false) {
break;
}
$rowCount++;
}
}
fclose($handle);
return $rowCount;
} catch (Exception $e) {
$this->errors[] = '导入失败: ' . $e->getMessage();
return false;
}
}
/**
* 下载文件
* @param string $filePath 文件路径
* @param boolean $deleteAfter 下载后是否删除
*/
public function downloadFile($filePath, $deleteAfter = false) {
if (!file_exists($filePath)) {
throw new Exception('文件不存在');
}
$filename = basename($filePath);
// 设置HTTP头
header('Content-Description: File Transfer');
header('Content-Type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
header('Content-Length: ' . filesize($filePath));
// 输出文件内容
readfile($filePath);
if ($deleteAfter) {
@unlink($filePath);
}
exit;
}
/**
* 清理文件名
* @param string $filename
* @return string
*/
private function checkFilename($filename) {
// 移除非法字符
$filename = preg_replace('/[^a-zA-Z0-9_\-\.]/', '_', $filename);
// 防止路径穿越
$filename = str_replace(['../', './', '..\\', '.\\'], '', $filename);
return $filename;
}
/**
* 获取错误信息
* @return array
*/
public function getErrors() {
return $this->errors;
}
/**
* 清除错误信息
*/
public function clearErrors() {
$this->errors = [];
}
/**
* 检查是否有错误
* @return boolean
*/
public function hasErrors() {
return !empty($this->errors);
}
}
?>
服务类封装(业务逻辑层)
<?php
/**
* 导入导出服务类
* 提供业务级别的导入导出功能
*/
class DataTransferService {
private $dte;
private $withHeaders = 'withHeaders'; // 隐私保护示例
/**
* 构造函数
*/
public function __construct($config = []) {
$this->dte = new DataExportImport($config);
}
/**
* 通用导出方法
* @param array $params 导出参数
* @return array [status, message, data]
*/
public function export($params) {
try {
$type = isset($params['type']) ? strtolower($params['type']) : 'csv';
$headers = isset($params['headers']) ? $params['headers'] : [];
$data = isset($params['data']) ? $params['data'] : [];
$filename = isset($params['filename']) ? $params['filename'] : '';
// 验证数据
if (empty($data)) {
return ['status' => false, 'message' => '没有数据可导出', 'data' => null];
}
// 根据类型导出
if ($type == 'excel') {
$result = $this->dte->exportToExcel($headers, $data, $filename);
} else {
$result = $this->dte->exportToCSV($headers, $data, $filename);
}
if ($result !== false) {
return ['status' => true, 'message' => '导出成功', 'data' => $result];
}
return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
} catch (Exception $e) {
return ['status' => false, 'message' => $e->getMessage(), 'data' => null];
}
}
/**
* 生成Excel模板
* @param array $headers 表头
* @param string $filename 文件名
* @return array
*/
public function exportTemplate($headers, $filename = '导入模板') {
try {
// 添加示例数据
$exampleData = [];
if ($this->isAssocArray($headers)) {
// 如果是关联数组,只有键作为表头
$headers = array_keys($headers);
}
// 生成示例数据(空一行)
foreach ($headers as $header) {
$exampleData[] = ['']; // 空示例行
}
// 创建模板
$result = $this->dte->exportToExcel($headers, $exampleData, $filename);
if ($result !== false) {
return ['status' => true, 'message' => '模板生成成功', 'data' => $result];
}
return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
} catch (Exception $e) {
return ['status' => false, 'message' => $e->getMessage(), 'data' => null];
}
}
/**
* 通用导入方法
* @param array $params 导入参数
* @return array [status, message, data]
*/
public function import($params) {
try {
// 上传文件
if (isset($params['file'])) {
$filePath = $this->dte->uploadFile($params['file']);
if ($filePath === false) {
return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
}
} elseif (isset($params['path'])) {
$filePath = $params['path'];
} else {
return ['status' => false, 'message' => '没有找到文件', 'data' => null];
}
// 获取文件类型
$type = isset($params['type']) ? strtolower($params['type']) :
strtolower(pathinfo($filePath, PATHINFO_EXTENSION));
// 字段映射
$mapping = isset($params['mapping']) ? $params['mapping'] : [];
// 根据类型导入
if ($type == 'csv') {
$data = $this->dte->importFromCSV($filePath, $mapping);
} else {
$data = $this->dte->importFromExcel($filePath, $mapping);
}
if ($data !== false) {
return ['status' => true, 'message' => '导入成功', 'data' => $data];
}
return ['status' => false, 'message' => $this->getErrorMessage(), 'data' => null];
} catch (Exception $e) {
return ['status' => false, 'message' => $e->getMessage(), 'data' => null];
}
}
/**
* 导入并处理(适用于文件导入数据库)
* @param string $filePath 文件路径
* @param string $type 文件类型
* @param callable $processor 处理函数
* @return int|false 处理成功条数
*/
public function importAndProcess($filePath, $type, $processor) {
try {
// 使用分批导入处理大文件
$result = $this->dte->importLargeData($filePath, function($row) use ($processor) {
// 调用处理函数
return call_user_func_array($processor, [$row]);
});
return $result;
} catch (Exception $e) {
return false;
}
}
/**
* 检查数组是否为关联数组
* @param array $arr
* @return boolean
*/
private function isAssocArray(array $arr) {
if (empty($arr)) {
return false;
}
return array_keys($arr) !== range(0, count($arr) - 1);
}
/**
* 获取错误信息
* @return string
*/
private function getErrorMessage() {
$errors = $this->dte->getErrors();
return $errors ? implode('; ', $errors) : '操作失败';
}
}
?>
实用示例
<?php
/**
* 使用示例
*/
// 配置
$config = [
'export_path' => dirname(__FILE__) . '/exports/',
'import_path' => dirname(__FILE__) . '/imports/',
'max_file_size' => 20971520, // 20MB
'chunk_size' => 500,
];
// 1. 基础使用 - 导出
$dte = new DataExportImport($config);
$headers = ['ID', '姓名', '邮箱', '电话'];
$data = [
[1, '张三', 'zhangsan@example.com', '13800000001'],
[2, '李四', 'lisi@example.com', '13800000002'],
[3, '王五', 'wangwu@example.com', '13800000003'],
];
// 导出Excel
$result = $dte->exportToExcel($headers, $data, '用户列表');
// 导出CSV
$result = $dte->exportToCSV($headers, $data, '用户列表');
// 2. 使用服务类 - 更高级的封装
$service = new DataTransferService($config);
$params = [
'type' => 'excel',
'headers' => ['ID', '名称', '价格'],
'data' => [[1, '商品A', 99.9], [2, '商品B', 199.9]],
'filename' => '商品列表'
];
$result = $service->export($params);
// 3. 导入示例
$file = [
'error' => UPLOAD_ERR_OK,
'size' => 1024 * 1024,
'tmp_name' => '/tmp/phpXXXX',
'name' => 'test.xlsx'
];
$params = [
'file' => $file,
'type' => 'xlsx',
'mapping' => [
'姓名' => 'name',
'邮箱' => 'email',
'电话' => 'phone'
]
];
$result = $service->import($params);
// 4. 大批量数据处理
$dte->exportLargeData(
function($page, $chunkSize) {
// 从数据库获取数据
$data = getDataFromDB($page, $chunkSize);
return $data;
},
null,
'大量数据导出'
);
// 5. 异常处理
if ($dte->hasErrors()) {
echo "错误信息: ";
print_r($dte->getErrors());
}
?>
Composer依赖
{
"require": {
"phpoffice/phpspreadsheet": "^1.24",
"ext-json": "*",
"ext-fileinfo": "*"
}
}
配置文件示例
<?php
return [
'export_path' => env('EXPORT_PATH', storage_path('exports')),
'import_path' => env('IMPORT_PATH', storage_path('imports')),
'max_file_size' => env('MAX_FILE_SIZE', 10485760),
'allowed_extensions' => ['xls', 'xlsx', 'csv'],
'chunk_size' => 1000,
'time_limit' => 300,
'csv_delimiter' => ',',
'csv_encoding' => 'utf-8',
];
?>
这套封装提供了:
- 灵活的导出功能 - 支持Excel和CSV格式
- 可靠的导入功能 - 支持文件上传、格式校验
- 大批量数据处理 - 分批处理防止内存溢出
- 完善的错误处理 - 明确区分错误类型
- 配置灵活 - 可自定义路径、大小限制等
使用时需要安装相应依赖库,推荐使用更好的PhpSpreadsheet库,这个封装类可以根据具体业务需求进行扩展和优化。