- 环境准备:安装 PhpSpreadsheet 库。
- 控制器代码:编写处理上传和导入逻辑的核心代码。
- 视图代码:创建上传表单页面。
- 模型代码:将数据保存到数据库。
- 完整示例与总结:整合所有代码并补充注意事项。
第一步:环境准备
-
ThinkPHP 项目:确保你已经有一个可运行的 ThinkPHP 项目。
(图片来源网络,侵删) -
安装 PhpSpreadsheet:我们使用 Composer 来安装这个库,在你的项目根目录下(与
composer.json同级)打开终端或命令行,运行以下命令:composer require phpoffice/phpspreadsheet
安装完成后,
vendor目录下会出现phpoffice/phpspreadsheet文件夹,composer.json会自动更新。
第二步:创建数据库和数据表
假设我们要导入的是用户信息,包含 name(姓名)、email(邮箱)和 age(年龄)。
- 创建一个
users表:CREATE TABLE `users` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL COMMENT '姓名', `email` varchar(100) NOT NULL COMMENT '邮箱', `age` int(11) DEFAULT NULL COMMENT '年龄', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', PRIMARY KEY (`id`), UNIQUE KEY `email` (`email`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
第三步:创建控制器
在 app/controller 目录下创建一个控制器,ExcelController.php。

<?php
namespace app\controller;
use app\BaseController;
use PhpOffice\PhpSpreadsheet\IOFactory;
use PhpOffice\PhpSpreadsheet\Reader\IReadFilter;
use think\facade\Db;
use think\exception\ValidateException;
class ExcelController extends BaseController
{
/**
* 显示上传表单
*/
public function index()
{
return view();
}
/**
* 处理 Excel 文件上传和数据导入
*/
public function import()
{
// 获取上传的文件
$file = request()->file('excel');
if (empty($file)) {
return json(['code' => 0, 'msg' => '请选择要导入的Excel文件']);
}
try {
// 验证文件
validate(['excel' => 'fileExt:xls,xlsx'])
->check(['excel' => $file]);
// 读取 Excel 文件
$reader = IOFactory::createReaderForFile($file->getPathname());
$reader->setReadDataOnly(true); // 只读取数据,不读取格式
$spreadsheet = $reader->load($file->getPathname());
$sheet = $spreadsheet->getActiveSheet();
// 获取最高行数
$highestRow = $sheet->getHighestRow();
// 从第二行开始读取(假设第一行是标题)
$successCount = 0;
$errorRows = [];
for ($row = 2; $row <= $highestRow; $row++) {
// 获取单元格数据
$name = $sheet->getCell('A' . $row)->getValue();
$email = $sheet->getCell('B' . $row)->getValue();
$age = $sheet->getCell('C' . $row)->getValue();
// 数据验证
if (empty($name) || empty($email)) {
$errorRows[] = "第 {$row} 行:姓名和邮箱不能为空";
continue;
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
$errorRows[] = "第 {$row} 行:邮箱格式不正确";
continue;
}
// 使用 Db 类进行数据插入
Db::name('users')->insert([
'name' => $name,
'email' => $email,
'age' => is_numeric($age) ? intval($age) : null,
]);
$successCount++;
}
$message = "导入成功!共处理 {$highestRow - 1} 行,成功导入 {$successCount} 条。";
if (!empty($errorRows)) {
$message .= "\n以下行导入失败:\n" . implode("\n", $errorRows);
}
return json(['code' => 1, 'msg' => $message]);
} catch (ValidateException $e) {
// 验证失败
return json(['code' => 0, 'msg' => $e->getError()]);
} catch (\Exception $e) {
// 其他异常
return json(['code' => 0, 'msg' => '导入失败:' . $e->getMessage()]);
}
}
}
代码解析:
index():用于显示上传表单。import():核心处理方法。request()->file('excel'):获取前端名为excel的文件。validate(...):使用 ThinkPHP 的验证器,限制文件后缀为xls或xlsx。IOFactory::createReaderForFile():根据文件类型自动创建合适的读取器。load():加载 Excel 文件。getActiveSheet():获取当前活动的工作表。getHighestRow():获取工作表的最高行数,用于循环。- 循环读取:从第 2 行开始(跳过标题行),通过
getCell('A' . $row)->getValue()获取 A、B、C 列的数据。 - 数据验证:对读取的数据进行简单的业务逻辑验证(如非空、邮箱格式)。
Db::name('users')->insert():使用 ThinkPHP 的数据库门面,将数据插入到users表。- 异常处理:使用
try...catch捕获可能发生的错误,如文件格式错误、数据验证失败、数据库操作失败等,并返回友好的提示。
第四步:创建视图文件
在 app/view 目录下创建一个与控制器同名的目录 excel,然后在里面创建 index.html 文件。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">ThinkPHP 导入 Excel 教程</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; margin: 20px; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px; }
h1, h2 { color: #333; }
.upload-area { border: 2px dashed #ccc; padding: 20px; text-align: center; margin-bottom: 20px; }
input[type="file"] { margin: 10px 0; }
button { background-color: #4CAF50; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
button:hover { background-color: #45a049; }
.result { margin-top: 20px; padding: 10px; border-radius: 4px; }
.success { background-color: #dff0d8; color: #3c763d; }
.error { background-color: #f2dede; color: #a94442; }
</style>
</head>
<body>
<div class="container">
<h1>用户信息导入</h1>
<p>请下载模板,填写数据后上传,Excel 第一行为标题(姓名, 邮箱, 年龄),从第二行开始为数据。</p>
<a href="/public/template.xlsx" download>点击下载 Excel 模板</a>
<div class="upload-area">
<form id="uploadForm" enctype="multipart/form-data">
<input type="file" name="excel" id="excel" accept=".xls,.xlsx" required>
<br>
<button type="submit">导入数据</button>
</form>
</div>
<div id="result" class="result" style="display: none;"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script>
$(function(){
$('#uploadForm').on('submit', function(e){
e.preventDefault(); // 阻止表单默认提交
var formData = new FormData(this);
var submitBtn = $(this).find('button');
submitBtn.prop('disabled', true).text('导入中...');
$.ajax({
url: '{:url("excel/import")}',
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(res) {
$('#result').removeClass('success error');
if(res.code === 1) {
$('#result').addClass('success').html('<strong>成功:</strong>' + res.msg).show();
} else {
$('#result').addClass('error').html('<strong>失败:</strong>' + res.msg).show();
}
},
error: function() {
$('#result').addClass('error').html('<strong>错误:</strong>服务器请求失败,请稍后再试。').show();
},
complete: function() {
submitBtn.prop('disabled', false).text('导入数据');
}
});
});
});
</script>
</body>
</html>
视图解析:
- 表单:
<form>标签设置了enctype="multipart/form-data",这是文件上传所必需的。 - 文件输入:
<input type="file" name="excel">,name属性必须与控制器中request()->file('excel')的参数一致。 - 模板下载:提供一个 Excel 模板的下载链接,方便用户按格式填写,你需要手动创建一个
public/template.xlsx文件,并填好标题行。 - AJAX 提交:使用 jQuery 的 AJAX 来异步提交表单,避免页面刷新,提升用户体验。
- 结果展示:通过一个
div来显示导入成功或失败的提示信息。
第五步:配置路由(可选但推荐)
为了方便访问,可以在 route/app.php 中配置路由:

use think\facade\Route;
// Excel 导入相关路由
Route::get('excel', 'ExcelController/index');
Route::post('excel/import', 'ExcelController/import');
配置后,访问 http://你的域名/excel 即可看到上传页面。
总结与注意事项
-
文件大小限制:PHP 默认的上传文件大小可能很小(如 2M),你需要在
php.ini中修改upload_max_filesize和post_max_size的值。upload_max_filesize = 20M post_max_size = 20M
修改后需要重启 PHP-FPM 或 Apache 服务。
-
内存占用:PhpSpreadsheet 在处理大型 Excel 文件时会占用较多内存,如果文件非常大,可以考虑:
- 服务器增加内存。
- 使用
setReadDataOnly(true)减少内存消耗。 - 对于超大数据,可能需要考虑更底层的处理方式或分批读取。
-
数据验证:本教程中的数据验证比较简单,在实际项目中,你可能需要更复杂的验证规则,例如检查手机号格式、身份证号、重复数据等。
-
事务处理:为了保证数据一致性,如果导入过程中某条数据出错,你可能希望回滚之前所有成功的导入,这时可以使用数据库事务。
Db::startTrans(); try { // ... 循环插入数据的代码 ... Db::commit(); return json(['code' => 1, 'msg' => '导入成功']); } catch (\Exception $e) { Db::rollback(); return json(['code' => 0, 'msg' => '导入失败:' . $e->getMessage()]); } -
性能优化:对于上万条数据,循环逐条插入数据库性能会很差,可以优化为批量插入:
$dataToInsert = []; for ($row = 2; $row <= $highestRow; $row++) { // ... 数据读取和验证 ... $dataToInsert[] = [ 'name' => $name, 'email' => $email, 'age' => $age, ]; } // 分批插入,例如每 500 条一批 array_chunk($dataToInsert, 500); foreach (array_chunk($dataToInsert, 500) as $chunk) { Db::name('users')->insertAll($chunk); }
至此,一个完整的 ThinkPHP 导入 Excel 功能就实现了,你可以根据这个基础框架进行扩展和优化。
