ThinkPHP项目Excel导入导出操作

wen PHP项目 3

本文目录导读:

ThinkPHP项目Excel导入导出操作

  1. 📑 目录导读
  2. 🧠 总结与启发

ThinkPHP项目Excel导入导出操作:从入门到企业级实战(含性能优化与坑点规避)


📑 目录导读

  1. Excel操作在ThinkPHP项目中的角色与选型
  2. 环境准备:Composer引入PhpSpreadsheet(替代已废弃的PHPExcel)
  3. 实战导出:多工作表、样式控制与百万数据流式输出
  4. 实战导入:批量数据校验、事务处理与错误回滚机制
  5. 性能优化:内存管理、分批查询与异步任务(Redis队列)
  6. 高频问题排查(Q&A)
  7. 安全与合规:文件上传校验、CSRF与权限控制

Excel操作在ThinkPHP项目中的角色与选型

在ERP、CRM或后台管理系统中,Excel导入导出几乎是刚需,ThinkPHP(6.x/8.x)作为国内主流PHP框架,常被用于快速构建这类业务。但请注意:原生PHPExcel已停止维护且存在PHP 8兼容性问题,目前社区统一推荐使用 PhpSpreadsheet(PHPExcel官方继承者),它支持读写xlsx、xls、csv等格式,且对内存占用和性能做了大幅优化。

选型建议:若只是简单CSV,用PHP原生fputcsv/fgetcsv即可;若涉及复杂样式、公式或大数据量,直接上PhpSpreadsheet。


环境准备:Composer引入PhpSpreadsheet

在ThinkPHP项目根目录执行:

composer require phpoffice/phpspreadsheet

安装后,在控制器中引入:

use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\IOFactory;

注意:若服务器禁用了exec函数,请确保PHP.ini中extension=zipextension=xml已开启,否则导出xlsx会报错。


实战导出:样式控制与流式输出

经典场景:导出用户表(含序号、姓名、手机、创建时间)至Excel。

核心代码示例

public function export()
{
    // 1. 从数据库获取数据(建议chunk分批,避免内存溢出)
    $data = Db::name('user')->select()->toArray();
    // 2. 创建Spreadsheet对象
    $spreadsheet = new Spreadsheet();
    $sheet = $spreadsheet->getActiveSheet();
    // 3. 设置标题栏(加粗、背景色)
    $header = ['ID', '姓名', '手机号', '注册时间'];
    foreach ($header as $key => $value) {
        $cell = $sheet->getCellByColumnAndRow($key + 1, 1);
        $cell->setValue($value);
        $cell->getStyle()->getFont()->setBold(true);
        $cell->getStyle()->getFill()->setFillType(\PhpOffice\PhpSpreadsheet\Style\Fill::FILL_SOLID);
        $cell->getStyle()->getFill()->getStartColor()->setARGB('FFFFCC00');
    }
    // 4. 写入数据(从第2行开始)
    $rowIndex = 2;
    foreach ($data as $row) {
        $sheet->setCellValue('A' . $rowIndex, $row['id']);
        $sheet->setCellValue('B' . $rowIndex, $row['name']);
        $sheet->setCellValue('C' . $rowIndex, $row['phone']);
        $sheet->setCellValue('D' . $rowIndex, date('Y-m-d', $row['create_time']));
        $rowIndex++;
    }
    // 5. 自动调整列宽
    foreach (range('A', 'D') as $col) {
        $sheet->getColumnDimension($col)->setAutoSize(true);
    }
    // 6. 输出下载
    header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    header('Content-Disposition: attachment;filename="用户列表.xlsx"');
    header('Cache-Control: max-age=0');
    $writer = IOFactory::createWriter($spreadsheet, 'Xlsx');
    $writer->save('php://output');
    exit;
}

改进点:使用setCellValueExplicit强制设置单元格为字符串,防止手机号被科学计数法转换。


实战导入:数据校验与事务回滚

坑点警示:Excel中日期、浮点数、空行经常导致导入数据结构异常,建议先统一转数组,再逐行校验。

推荐流程

public function import()
{
    // 1. 接收前端上传的Excel文件(ThinkPHP验证器)
    $file = request()->file('excel');
    $validate = Validate::rule(['excel' => 'fileSize:5M|fileExt:xlsx,csv'])->check(['excel' => $file]);
    // 2. 读取内容
    $spreadsheet = IOFactory::load($file->getPathname());
    $data = $spreadsheet->getActiveSheet()->toArray(null, true, true, true);
    // 3. 去除表头,启动事务
    Db::startTrans();
    try {
        $errors = [];
        foreach ($data as $rowNum => $row) {
            if ($rowNum == 1) continue; // 跳过表头
            if (empty($row['A'])) continue; // 跳过空行
            // 4. 字段校验(示例:手机号格式)
            if (!preg_match('/^1[3-9]\d{9}$/', $row['C'])) {
                $errors[] = "第{$rowNum}行手机号格式错误";
                continue;
            }
            // 5. 插入数据库
            Db::name('user')->insert([
                'name' => $row['B'],
                'phone' => $row['C'],
            ]);
        }
        // 6. 若有错误,记录并提示,但业务成功部分已入库
        Db::commit();
        return json(['code' => 1, 'msg' => '成功导入' . (count($data)-1-count($errors)) . '条,失败' . count($errors) . '条', 'errors' => $errors]);
    } catch (\Exception $e) {
        Db::rollback();
        return json(['code' => 0, 'msg' => '导入失败:' . $e->getMessage()]);
    }
}

进阶技巧:给每行数据增加MD5唯一键,实现“重复导入自动更新”的幂等操作。


性能优化:大数据量内存控制

普通load方法在导入10万行时,内存可能直接飙到256M以上。解决方案:使用PhpSpreadsheet的ReadFilter配合setReadDataOnly(true),或开启“行迭代器”模式。

推荐实现

$reader = IOFactory::createReaderForFile($filePath);
$reader->setReadDataOnly(true);
$reader->setReadEmptyCells(false);
// 只读取指定的列区域(大幅降低内存)
$filter = new class implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter {
    public function readCell($column, $row, $worksheetName = '') : bool {
        return in_array($column, ['A', 'B', 'C', 'D']);
    }
};
$reader->setReadFilter($filter);
$spreadsheet = $reader->load($filePath);

对于导出百万数据,建议使用PhpSpreadsheetsetPreCalculateFormulas(false),并配合ThinkPHP的chunk(500)分批查询,避免一次性载入所有记录。


高频问题排查(Q&A)

Q1:导出报“Class 'ZipArchive' not found”怎么办? 答:Linux下执行yum install php-zipapt-get install php-zip,Windows开启php_zip.dll扩展,同时确保PHP版本≥7.4。

Q2:导入的日期字段变成一串数字(如44562)? 答:这是Excel内部时间戳,转换方法:$timestamp = ($value - 25569) * 86400; 然后用date('Y-m-d', $timestamp)格式化,或者用PhpSpreadsheet的\PhpOffice\PhpSpreadsheet\Shared\Date::tryStringToExcel()

Q3:大数据导出时浏览器超时或中断? 答:在控制器开头设置set_time_limit(0)ini_set('memory_limit', '512M'),更保险做法:使用fastcgi_finish_request()在输出CSV前关闭连接,后台异步生成文件。

Q4:如何防止用户在Excel中输入恶意公式(CSV注入)? 答:在导入时,检测单元格内容是否以, , , 开头,若是则在前加单引号,或者转义为\t,导出时,对用户提供的字符串内容强制设为基础格式setCellValueExplicit($cell, $value, DataType::TYPE_STRING)


安全与合规:文件上传校验与权限控制

  • 文件类型校验:不要轻信扩展名,使用finfo_file等MIME类型检测。
  • 文件大小限制:ThinkPHP验证器加入fileSize规则,防止上传超大文件耗尽PHP内存。
  • CSRF防护:在Form表单中加入{:token()},控制器使用validate()内置规则。
  • 权限控制:建议将导入导出类操作放入独立中间件,做RBAC权限校验(如只允许运营角色操作)。

🧠 总结与启发

ThinkPHP项目中的Excel操作,核心难点不在API使用,而在于数据健壮性、内存控制与异常处理,记住三个关键动作:读入前先筛选列、写入前先强制字符串、导入前先事务包裹

最后给一个实战建议:不要在前端一次性请求中做百万级导入,更好的架构是:上传Excel后,后台脚本将数据解析为JSON存入Redis列表,再由think-queue消费者逐条写入数据库,这样既避免超时,又方便重试机制,这也是大型电商平台的通用方案。


(全文完)

抱歉,评论功能暂时关闭!