Skip to content
carlin-rj edited this page Nov 3, 2023 · 1 revision

快速启动

该文件可以在以下位置找到app/Imports:

.
├── app
│   ├── Imports
│   │   ├── UsersImport.php
│ 
└── composer.json


<?php

namespace App\Imports;

use App\User;
use Illuminate\Support\Facades\Hash;
use Mckue\Excel\Concerns\ToModel;

class UsersImport implements ToModel
{
    /**
     * @param array $row
     *
     * @return User|null
     */
    public function model(array $row)
    {
        return new User([
           'name'     => $row[0],
           'email'    => $row[1],
           'password' => Hash::make($row[2]),
        ]);
    }
}

从默认磁盘导入

将 UsersImport 对象传递给该Excel::import()方法将告诉包如何导入作为第二个参数传递的文件。该文件预计位于您的默认文件系统磁盘中(请参阅 参考资料config/filesystems.php)。

从另一个磁盘导入

Excel::import(new UsersImport, 'users.xlsx', 's3');

如果您让用户上传文档,您也可以直接传递上传的文件。

Excel::import(new UsersImport, request()->file('your_file'));

导入完整路径

如果您想指定文件所在的路径,而不必将其移动到磁盘,则可以直接将该文件路径传递给导入方法。

Excel::import(new UsersImport, storage_path('users.xlsx'));

导入到数组或集合

如果您想绕过ToArrayorToCollection问题并希望在控制器中拥有一组导入的数据(注意性能!),您可以使用 or::toArray()方法::toCollection()。

$array = Excel::toArray(new UsersImport, 'users.xlsx');

$collection = Excel::toCollection(new UsersImport, 'users.xlsx');

导入到集合

开始导入的最简单方法是创建自定义导入类。我们将使用用户导入作为示例。

UsersImport创建一个名为的新类app/Imports:

namespace App\Imports;

use App\User;
use Illuminate\Support\Collection;
use Mckue\Excel\Concerns\ToCollection;

class UsersImport implements ToCollection
{
    public function collection(Collection $rows)
    {
        foreach ($rows as $row) 
        {
            User::create([
                'name' => $row[0],
            ]);
        }
    }
}

集合方法将接收行的集合。行是一个充满单元格值的数组。

如果文件有多张,该collection()方法将被多次调用。

在您的控制器中,我们现在可以导入:

public function import() 
{
    Excel::import(new UsersImport, 'users.xlsx');
}

无论您在collection()方法中返回什么,都不会返回到控制器。

导入到模型

如果您想将工作簿导入 Eloquent 模型,您可以使用ToModel关注点。该关注点强制执行一种model()接受要返回的模型的方法。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;

class UsersImport implements ToModel
{
    public function model(array $row)
    {
        return new User([
            'name' => $row[0],
        ]);
    }
}

返回的模型将为您保存。每一行将导致(至少)一次保存,并且还将触发模型事件。

如果您想要更新插入模型,而不是插入,您可以实现该WithUpserts关注点。

class UsersImport implements ToModel, WithUpserts
{
    /**
     * @return string|array
     */
    public function uniqueBy()
    {
        return 'email';
    }
}

在上面的示例中,如果已存在具有相同电子邮件地址的用户,则该行将被更新。在幕后,此功能使用 Laravelupsert方法,并且该uniqueBy方法用于该upsert方法的第二个参数,该方法列出了唯一标识关联表中记录的列。

使用特定列更新插入

class UsersImport implements ToModel, WithUpserts, WithUpsertColumns
{
    /**
     * @return array
     */
    public function upsertColumns()
    {
        return ['name', 'role'];
    }
}

在此示例中,如果用户已存在,则仅更新“name”和“role”列。

跳行

如果你想跳过一行,可以返回 null。

public function model(array $row)
{
    if (!isset($row[0])) {
        return null;
    }

    return new User([
        'name' => $row[0],
    ]);
}

自行处理持久性

在某些情况下,您可能没有每行都是 Eloquent 模型的导入,并且您希望更好地控制所发生的情况。在这些情况下,您可以使用OnEachRow关注点。

namespace App\Imports;

use App\Group;
use App\User;
use Mckue\Excel\Row;
use Mckue\Excel\Concerns\OnEachRow;

class UsersImport implements OnEachRow
{
    public function onRow(array $row)
    {
        $rowIndex = $row->getIndex();
        $row      = $row->toArray();
        
        $group = Group::firstOrCreate([
            'name' => $row[1],
        ]);
    
        $group->users()->create([
            'name' => $row[0],
        ]);
    }
}

使用时OnEachRow不能使用批量插入

Importables

在前面的示例中,我们使用Excel::import 门面来开始导入。

Laravel Excel 还提供了一个Muckue\Excel\Concerns\Importable特征,使导入类可导入。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\Importable;

class UsersImport implements ToModel
{
    use Importable;

    public function model(array $row)
    {
        return new User([
            'name' => $row[0],
        ]);
    }
}

导入

我们现在可以导入而不需要外观:

(new UsersImport)->import('users.xlsx', 'local', \Mckue\Excel\Excel::XLSX);

to Array

导入可以加载到数组中

$array = (new UsersImport)->toArray('users.xlsx');

To collection

导入可以加载到集合中

$collection = (new UsersImport)->toCollection('users.xlsx');

导入格式

目前只能导入xlsx

多个电子表格

当一个文件有多个工作表时,每个工作表都会经过导入对象。如果您想单独处理每张工作表,则需要实现该WithMultipleSheets关注点。

该sheets()方法期望返回工作表导入对象的数组。工作表的顺序很重要,数组中的第一个工作表导入对象将自动链接到 Excel 文件中的第一个工作表。

namespace App\Imports;

use Mckue\Excel\Concerns\WithMultipleSheets;

class UsersImport implements WithMultipleSheets 
{
   
    public function sheets(): array
    {
        return [
            new FirstSheetImport()
        ];
    }
}

工作表导入类可以导入与普通导入对象相同的关注点。

namespace App\Imports;

use Illuminate\Support\Collection;
use Mckue\Excel\Concerns\ToCollection;

class FirstSheetImport implements ToCollection
{
    public function collection(Collection $rows)
    {
        //
    }
}

按工作表索引选择工作表

如果您想要更多地控制选择哪些工作表以及如何将它们映射到特定工作表导入对象,您可以使用工作表索引作为键。图纸索引从 0 开始。

namespace App\Imports;

use Mckue\Excel\Concerns\WithMultipleSheets;

class UsersImport implements WithMultipleSheets 
{
   
    public function sheets(): array
    {
        return [
            0 => new FirstSheetImport(),
            1 => new SecondSheetImport(),
        ];
    }
}

按工作表名称选择工作表

如果只知道工作表名称而不知道工作表索引,也可以使用工作表名称作为选择器。将工作表名称作为数组索引,以将该工作表链接到工作表导入对象。

namespace App\Imports;

use Mckue\Excel\Concerns\WithMultipleSheets;

class UsersImport implements WithMultipleSheets 
{
    public function sheets(): array
    {
        return [
            'Worksheet 1' => new FirstSheetImport(),
            'Worksheet 2' => new SecondSheetImport(),
        ];
    }
}

方法中未明确定义的工作表sheet()将被忽略,因此不会被导入。

跳过未知的工作表

当您定义的工作表名称或索引不存在时Mckue\Excel\Exceptions\SheetNotFoundException,将会抛出异常。

如果您想在工作表不存在时忽略,可以使用Mckue\Excel\Concerns\SkipsUnknownSheets关注点。

namespace App\Imports;

use Mckue\Excel\Concerns\WithMultipleSheets;
use Mckue\Excel\Concerns\SkipsUnknownSheets;

class UsersImport implements WithMultipleSheets, SkipsUnknownSheets
{
    public function sheets(): array
    {
        return [
            'Worksheet 1' => new FirstSheetImport(),
            'Worksheet 2' => new SecondSheetImport(),
        ];
    }
    
    public function onUnknownSheet($sheetName)
    {
        // E.g. you can log that a sheet was not found.
        info("Sheet {$sheetName} was skipped");
    }
}

仅跳过特定工作表

如果你想要 1 个可选工作表而其他工作表仍然失败,你也可以让工作表导入对象实现SkipsUnknownSheets.

namespace App\Imports;

use Mckue\Excel\Concerns\SkipsUnknownSheets;

class FirstSheetImport implements SkipsUnknownSheets
{
    public function onUnknownSheet($sheetName)
    {
        // E.g. you can log that a sheet was not found.
        info("Sheet {$sheetName} was skipped");
    }
}

现在只有FirstSheetImport在没有找到的情况下才会被跳过。将跳过任何其他定义的工作表。

批量插入

将大文件导入 Eloquent 模型可能很快就会成为瓶颈,因为每一行都会生成插入查询。

考虑到这一点,WithBatchInserts您可以通过指定批处理大小来限制执行的查询量。该批量大小将决定一次将有多少模型插入到数据库中。这将大大缩短导入时间。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\WithBatchInserts;

class UsersImport implements ToModel, WithBatchInserts
{
    public function model(array $row)
    {
        return new User([
            'name' => $row[0],
        ]);
    }
    
    public function batchSize(): int
    {
        return 1000;
    }
}

这种关注只有在关注的情况下才有效ToModel。

批量大小

批量大小1000并不是您导入的最佳情况。尝试使用这个数字来找到最佳点。

批量更新插入

对于批量更新插入,您还可以执行此WithUpserts操作。

class UsersImport implements ToModel, WithBatchInserts, WithUpserts
{
    public function model(array $row)
    {
        return new User([
            'name' => $row[0],
        ]);
    }
    
    public function batchSize(): int
    {
        return 1000;
    }

    public function uniqueBy()
    {
        return 'email';
    }
}

在上面的示例中,如果已存在具有相同电子邮件地址的用户,则该行将被更新。在幕后,此功能使用 Laravelupsert方法,并且该uniqueBy方法用于该upsert方法的第二个参数,该方法列出了唯一标识关联表中记录的列。

分块读取

导入大文件会对内存使用产生巨大影响,因为库会尝试将整个工作表加载到内存中。

为了缓解内存使用量的增加,您可以使用WithChunkReading关注点。这将以块的形式读取电子表格并控制内存使用。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\WithChunkReading;

class UsersImport implements ToModel, WithChunkReading
{
    public function model(array $row)
    {
        return new User([
            'name' => $row[0],
        ]);
    }
    
    public function chunkSize(): int
    {
        return 1000;
    }
}

块大小1000不是导入的最佳情况。尝试使用这个数字来找到最佳点。

与批量插入一起使用

当结合批量插入和块读取时,您会发现最理想的情况(关于时间和内存消耗)。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\WithBatchInserts;
use Mckue\Excel\Concerns\WithChunkReading;

class UsersImport implements ToModel, WithBatchInserts, WithChunkReading
{
    public function model(array $row)
    {
        return new User([
            'name' => $row[0],
        ]);
    }
    
    public function batchSize(): int
    {
        return 1000;
    }
    
    public function chunkSize(): int
    {
        return 1000;
    }
}

如果您需要行号,可以使用RemembersRowNumber Trait。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\RemembersRowNumber;
use Mckue\Excel\Concerns\WithChunkReading;

class UsersImport implements ToModel, WithChunkReading
{
    use RemembersRowNumber;

    public function model(array $row)
    {
        $currentRowNumber = $this->getRowNumber();

        return new User([
            'name' => $row[0],
        ]);
    }
    
    public function chunkSize(): int
    {
        return 1000;
    }
}

记住行号仅适用于 ToModel 导入。

如果您只需要有关块的偏移量的信息,则可以使用 Trait RemembersChunkOffset。

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\RemembersChunkOffset;
use Mckue\Excel\Concerns\WithChunkReading;

class UsersImport implements ToModel, WithChunkReading
{
    use RemembersChunkOffset;

    public function model(array $row)
    {
        $chunkOffset = $this->getChunkOffset();

        return new User([
            'name' => $row[0],
        ]);
    }
    
    public function chunkSize(): int
    {
        return 1000;
    }
}

行验证

有时您可能希望在将每一行插入数据库之前对其进行验证。通过实现WithValidation关注点,您可以指示每行需要遵守的规则。

该rules()方法期望返回一个包含 Laravel 验证规则的数组。

<?php

namespace App\Imports;

use App\User;
use Illuminate\Validation\Rule;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\WithValidation;

class UsersImport implements ToModel, WithValidation
{
    use Importable;

    public function model(array $row)
    {
        return new User([
            'name'     => $row[0],
            'email'    => $row[1],
            'password' => 'secret',
        ]);
    }

    public function rules(): array
    {
        return [
            '1' => Rule::in(['patrick@Mckue.nl']),

             // Above is alias for as it always validates in batches
             '*.1' => Rule::in(['patrick@Mckue.nl']),
             
             // Can also use callback validation rules
             '0' => function($attribute, $value, $onFailure) {
                  if ($value !== 'Patrick Brouwers') {
                       $onFailure('Name is not Patrick Brouwers');
                  }
              }
        ];
    }
}

使用标题行进行验证

使用WithHeadingRow关注点时,可以使用标题行名称作为规则属性。

<?php

namespace App\Imports;

use App\User;
use Illuminate\Validation\Rule;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\WithValidation;
use Mckue\Excel\Concerns\WithHeadingRow;

class UsersImport implements ToModel, WithValidation, WithHeadingRow
{
    use Importable;

    public function model(array $row)
    {
        return new User([
            'name'     => $row['name'],
            'email'    => $row['email'],
            'password' => 'secret',
        ]);
    }

    public function rules(): array
    {
        return [
            'email' => Rule::in(['patrick@Mckue.nl']),

             // Above is alias for as it always validates in batches
             '*.email' => Rule::in(['patrick@Mckue.nl']),
        ];
    }
}

如果您的验证规则引用了其他字段名称,如different、lt、lte、gt、gte和same规则中的字段名称,则字段名称必须带有前缀*.,如下例所示,因为验证是批量完成的。

public function rules(): array
{
    return [
        'maximum' => 'gte:*.minimum',
    ];
}

自定义验证消息

通过customValidationMessages()向导入添加方法,您可以为每次失败指定自定义消息。

/**
* @return array
*/
public function rules(): array
{
    return [
        '1' => Rule::in(['patrick@Mckue.nl']),
    ];
}

/**
 * @return array
 */
public function customValidationMessages()
{
    return [
        '1.in' => 'Custom message for :attribute.',
    ];
}

自定义验证属性

通过customValidationAttributes()向导入添加方法,您可以为每列指定自定义属性名称。

/**
* @return array
*/
public function rules(): array
{
    return [
        '1' => Rule::in(['patrick@Mckue.nl']),
    ];
}

/**
 * @return array
 */
public function customValidationAttributes()
{
    return ['1' => 'email'];
}

处理验证错误

数据库事务

整个导入自动包装在数据库事务中,这意味着每个错误都会回滚整个导入。使用批量插入时,仅回滚当前批次。

禁用事务

如果您不想在导入(或块导入)周围有任何数据库事务,您可以更改要在配置中使用的事务处理程序: 在config/Mckue-excel.php:

'transactions' => [
    'handler' => 'db',
],

目前支持的处理程序有:null或db。

自定义事务程序 如果您想要自定义事务处理程序(例如 MongoDB 数据库),您可以添加自己的处理程序:

$this->app->make(\Mckue\Excel\Transactions\TransactionManager::class)->extend('your_handler', function() {
    return new YourTransactionHandler();
});

处理程序应该实现Mckue\Excel\Transactions\TransactionHandler.

最后收集所有的失败

与批量插入结合使用时,您可以在导入结束时收集所有验证失败。您可以尝试捕获ValidationException. 在此异常中,您可以获得所有失败。

每个失败都是一个实例Mckue\Excel\Validators\Failure。保存Failure有关该单元格的哪一行、哪一列以及验证错误的信息。

try {
    $import->import('import-users.xlsx');
} catch (\Mckue\Excel\Validators\ValidationException $e) {
     $failures = $e->failures();
     
     foreach ($failures as $failure) {
         $failure->row(); // row that went wrong
         $failure->attribute(); // either heading key (if using heading row concern) or column index
         $failure->errors(); // Actual error messages from Laravel validator
         $failure->values(); // The values of the row that has failed.
     }
}

跳过失败

有时您可能想跳过失败。通过使用SkipsOnFailure关注点,您可以控制验证失败时发生的情况。当使用SkipsOnFailure整个导入时出现故障时不会回滚。

<?php

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Validators\Failure;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\SkipsOnFailure;
use Mckue\Excel\Concerns\WithValidation;

class UsersImport implements ToModel, WithValidation, SkipsOnFailure
{
    use Importable;

    /**
     * @param Failure[] $failures
     */
    public function onFailure(Failure ...$failures)
    {
        // Handle the failures how you'd like.
    }
}

如果您想自动跳过所有失败的行并在导入结束时收集失败的行,则可以使用该Mckue\Excel\Concerns\SkipsFailures特征。

<?php

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Validators\Failure;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\SkipsOnFailure;
use Mckue\Excel\Concerns\WithValidation;
use Mckue\Excel\Concerns\SkipsFailures;

class UsersImport implements ToModel, WithValidation, SkipsOnFailure
{
    use Importable, SkipsFailures;
}

未通过验证规则的每一行都将被跳过。我们现在可以收集最后所有的失败:

$import = new UsersImport();
$import->import('users.xlsx');

foreach ($import->failures() as $failure) {
     $failure->row(); // row that went wrong
     $failure->attribute(); // either heading key (if using heading row concern) or column index
     $failure->errors(); // Actual error messages from Laravel validator
     $failure->values(); // The values of the row that has failed.
}

跳过空行 有时您可能想跳过空行,例如在使用required验证规则时。通过使用关注点,在验证和导入SkipsEmptyRows期间将跳过空行。

<?php

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\SkipsEmptyRows;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\WithHeadingRow;
use Mckue\Excel\Concerns\WithValidation;

class UsersImport implements ToModel, SkipsEmptyRows, WithHeadingRow, WithValidation
{
    use Importable;
    
    public function rules(): array
    {
        return [
            'name' => [
                'required',
                'string',
            ],
        ];
    }
}

此外,SkipsEmptyRows您还可以通过isEmptyWhen在导入器中拥有自己的逻辑来跳过行

<?php

namespace App\Imports;

use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\ToArray;

class UsersImport implements ToArray,
{
    use Importable;
    
    public function isEmptyWhen(array $row): bool
    {
        return $row['name'] === 'John Doe';
    }
}

跳过错误

有时您可能想跳过所有错误,例如重复的数据库记录。通过使用SkipsOnError关注点,您可以控制模型导入失败时发生的情况。当使用SkipsOnError整个导入时,数据库发生异常时不会回滚。

<?php

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\SkipsOnError;
use Mckue\Excel\Concerns\WithValidation;

class UsersImport implements ToModel, WithValidation, SkipsOnError
{
    use Importable;

    /**
     * @param \Throwable $e
     */
    public function onError(\Throwable $e)
    {
        // Handle the exception how you'd like.
    }
}

如果您想自动跳过所有异常并在导入结束时收集它们,则可以使用该Mckue\Excel\Concerns\SkipsErrors特征。

<?php

namespace App\Imports;

use App\User;
use Mckue\Excel\Concerns\ToModel;
use Mckue\Excel\Validators\Failure;
use Mckue\Excel\Concerns\Importable;
use Mckue\Excel\Concerns\SkipsOnError;
use Mckue\Excel\Concerns\WithValidation;
use Mckue\Excel\Concerns\SkipsErrors;

class UsersImport implements ToModel, WithValidation, SkipsOnError
{
    use Importable, SkipsErrors;
}

每一行出错的行都将被跳过。我们现在可以在最后收集所有错误:

$import = new UsersImport();
$import->import('users.xlsx');

dd($import->errors());

没有 ToModel 的行验证

如果您不使用ToModel关注点,则只需使用 Laravel 验证器即可非常轻松地进行行验证。

<?php

namespace App\Imports;

use App\User;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Validator;
use Mckue\Excel\Concerns\ToCollection;

class UsersImport implements ToCollection
{
    public function collection(Collection $rows)
    {
         Validator::make($rows->toArray(), [
             '*.0' => 'required',
         ])->validate();

        foreach ($rows as $row) {
            User::create([
                'name' => $row[0],
            ]);
        }
    }
}

准备数据进行验证

有时数据可能无法直接通过验证,但仍然有效。发生这种情况时,您可能需要在将数据发送到验证器之前稍微调整数据,为此,您可以prepareForValidation在导入中添加一个方法,该方法接收行数据以及行号,并应返回操纵的行数据。

class UsersImport implements WithValidation
{
    public function prepareForValidation($data, $index)
    {
        $data['email'] = $data['email'] ?? $this->myOtherWayOfFindingTheEmail($data);
        
        return $data;
    }
}

配置验证器

如果您想添加无法通过规则表达的条件验证或复杂验证,您可以配置验证器,类似于使用表单请求执行此操作的方式

手动验证 您可以使用它$validator->getData()来访问正在验证的数据

class UsersImport implements WithValidation
{
    public function withValidator($validator)
    {
        $validator->after(function ($validator) {
            if ($this->somethingElseIsInvalid()) {
                $validator->errors()->add('field', 'Something is wrong with this field!');
            }
        });

        // or...

        $validator->sometimes('*.email', 'required', $this->someConditionalRequirement());
    }
}

验证规则 所有验证规则的列表,请参考Laravel 文档 (打开新窗口)

跨多行验证 检查多行的验证规则(例如)仅在使用或关注distinct时才有效。WithBatchInsertsToCollection

导入接口

Mckue\Excel\Concerns\ToCollection 导入到集合中。
Mckue\Excel\Concerns\ToArray 导入到数组。
Mckue\Excel\Concerns\ToModel 将每一行导入到模型中。
Mckue\Excel\Concerns\OnEachRow 手动处理每一行。
Mckue\Excel\Concerns\WithBatchInserts 批量插入模型。
Mckue\Excel\Concerns\WithChunkReading 分块阅读该表。
Mckue\Excel\Concerns\WithHeadingRow 定义一行作为标题行。
Mckue\Excel\Concerns\WithLimit 定义需要导入的行数限制。
Mckue\Excel\Concerns\WithMappedCells 定义自定义单元格映射。
Mckue\Excel\Concerns\WithMapping 在 ToModel/ToCollection 中调用之前映射行。
Mckue\Excel\Concerns\WithMultipleSheets 启用多页支持。每张表都可以有自己的关注点(除了这张表)。
Mckue\Excel\Concerns\WithEvents 注册事件
Mckue\Excel\Concerns\WithStartRow 定义自定义起始行。
Mckue\Excel\Concerns\WithUpserts 允许更新插入模型。
Mckue\Excel\Concerns\WithUpsertColumns 允许更新插入列定义。
Mckue\Excel\Concerns\WithValidation 根据一组规则验证每一行。
Mckue\Excel\Concerns\SkipsEmptyRows 跳过空行。
Mckue\Excel\Concerns\SkipsOnFailure 跳过验证错误。
Mckue\Excel\Concerns\SkipsOnError 跳过数据库异常。
Mckue\Excel\Concerns\WithColumnLimit 允许设置结束列